ERP-node/frontend/components/screen/panels/DetailSettingsPanel.tsx

1064 lines
47 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React from "react";
import { Settings } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useWebTypes } from "@/hooks/admin/useWebTypes";
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
import {
ComponentData,
WidgetComponent,
FileComponent,
WebTypeConfig,
TableInfo,
LayoutComponent,
} from "@/types/screen";
// 레거시 ButtonConfigPanel 제거됨
import { FileComponentConfigPanel } from "./FileComponentConfigPanel";
// 새로운 컴포넌트 설정 패널들 import
import { ButtonConfigPanel as NewButtonConfigPanel } 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";
interface DetailSettingsPanelProps {
selectedComponent?: ComponentData;
onUpdateProperty: (componentId: string, path: string, value: any) => void;
currentTable?: TableInfo; // 현재 화면의 테이블 정보
currentTableName?: string; // 현재 화면의 테이블명
}
export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
selectedComponent,
onUpdateProperty,
currentTable,
currentTableName,
}) => {
// 데이터베이스에서 입력 가능한 웹타입들을 동적으로 가져오기
const { webTypes } = useWebTypes({ active: "Y" });
console.log(`🔍 DetailSettingsPanel props:`, {
selectedComponent: selectedComponent?.id,
componentType: selectedComponent?.type,
currentTableName,
currentTable: currentTable?.tableName,
selectedComponentTableName: selectedComponent?.tableName,
});
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);
// 레이아웃 컴포넌트 설정 렌더링 함수
const renderLayoutConfig = (layoutComponent: LayoutComponent) => {
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">
{layoutComponent.layoutType}
</span>
</div>
<div className="mt-1 text-xs text-gray-500">ID: {layoutComponent.id}</div>
</div>
{/* 레이아웃 설정 영역 */}
<div className="flex-1 space-y-4 overflow-y-auto p-4">
{/* 기본 정보 */}
<div>
<label className="mb-2 block text-sm font-medium text-gray-700"> </label>
<input
type="text"
value={layoutComponent.label || ""}
onChange={(e) => onUpdateProperty(layoutComponent.id, "label", e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
placeholder="레이아웃 이름을 입력하세요"
/>
</div>
{/* 그리드 레이아웃 설정 */}
{layoutComponent.layoutType === "grid" && (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> </label>
<input
type="number"
min="1"
max="10"
value={layoutComponent.layoutConfig?.grid?.rows || 2}
onChange={(e) => {
const newRows = parseInt(e.target.value);
const newCols = layoutComponent.layoutConfig?.grid?.columns || 2;
// 그리드 설정 업데이트
onUpdateProperty(layoutComponent.id, "layoutConfig.grid.rows", newRows);
// 존 개수 자동 업데이트 (행 × 열)
const totalZones = newRows * newCols;
const currentZones = layoutComponent.zones || [];
if (totalZones !== currentZones.length) {
const newZones = [];
for (let row = 0; row < newRows; row++) {
for (let col = 0; col < newCols; col++) {
const zoneIndex = row * newCols + col;
newZones.push({
id: `zone${zoneIndex + 1}`,
name: `${zoneIndex + 1}`,
position: { row, column: col },
size: { width: "100%", height: "100%" },
});
}
}
onUpdateProperty(layoutComponent.id, "zones", newZones);
}
}}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> </label>
<input
type="number"
min="1"
max="10"
value={layoutComponent.layoutConfig?.grid?.columns || 2}
onChange={(e) => {
const newCols = parseInt(e.target.value);
const newRows = layoutComponent.layoutConfig?.grid?.rows || 2;
// 그리드 설정 업데이트
onUpdateProperty(layoutComponent.id, "layoutConfig.grid.columns", newCols);
// 존 개수 자동 업데이트 (행 × 열)
const totalZones = newRows * newCols;
const currentZones = layoutComponent.zones || [];
if (totalZones !== currentZones.length) {
const newZones = [];
for (let row = 0; row < newRows; row++) {
for (let col = 0; col < newCols; col++) {
const zoneIndex = row * newCols + col;
newZones.push({
id: `zone${zoneIndex + 1}`,
name: `${zoneIndex + 1}`,
position: { row, column: col },
size: { width: "100%", height: "100%" },
});
}
}
onUpdateProperty(layoutComponent.id, "zones", newZones);
}
}}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> (px)</label>
<input
type="number"
min="0"
max="50"
value={layoutComponent.layoutConfig?.grid?.gap || 16}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.grid.gap", parseInt(e.target.value))
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
</div>
)}
{/* 플렉스박스 레이아웃 설정 */}
{layoutComponent.layoutType === "flexbox" && (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"></label>
<select
value={layoutComponent.layoutConfig?.flexbox?.direction || "row"}
onChange={(e) => {
const newDirection = e.target.value;
console.log("🔄 플렉스박스 방향 변경:", newDirection);
// 방향 설정 업데이트
onUpdateProperty(layoutComponent.id, "layoutConfig.flexbox.direction", newDirection);
// 방향 변경 시 존 크기 자동 조정
const currentZones = layoutComponent.zones || [];
const zoneCount = currentZones.length;
if (zoneCount > 0) {
const updatedZones = currentZones.map((zone, index) => ({
...zone,
size: {
...zone.size,
width: newDirection === "row" ? `${100 / zoneCount}%` : "100%",
height: newDirection === "column" ? `${100 / zoneCount}%` : "auto",
},
}));
console.log("🔄 존 크기 자동 조정:", {
direction: newDirection,
zoneCount,
updatedZones: updatedZones.map((z) => ({ id: z.id, size: z.size })),
});
onUpdateProperty(layoutComponent.id, "zones", updatedZones);
}
}}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value="row"> (row)</option>
<option value="column"> (column)</option>
<option value="row-reverse"> </option>
<option value="column-reverse"> </option>
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> </label>
<div className="flex items-center space-x-2">
<input
type="number"
min="1"
max="10"
value={layoutComponent.zones?.length || 2}
onChange={(e) => {
const newZoneCount = parseInt(e.target.value);
const currentZones = layoutComponent.zones || [];
const direction = layoutComponent.layoutConfig?.flexbox?.direction || "row";
if (newZoneCount > currentZones.length) {
// 존 추가
const newZones = [...currentZones];
for (let i = currentZones.length; i < newZoneCount; i++) {
newZones.push({
id: `zone${i + 1}`,
name: `${i + 1}`,
position: {},
size: {
width: direction === "row" ? `${100 / newZoneCount}%` : "100%",
height: direction === "column" ? `${100 / newZoneCount}%` : "100%",
},
});
}
// 기존 존들의 크기도 조정
newZones.forEach((zone, index) => {
if (direction === "row") {
zone.size.width = `${100 / newZoneCount}%`;
} else {
zone.size.height = `${100 / newZoneCount}%`;
}
});
onUpdateProperty(layoutComponent.id, "zones", newZones);
} else if (newZoneCount < currentZones.length) {
// 존 제거
const newZones = currentZones.slice(0, newZoneCount);
// 남은 존들의 크기 재조정
newZones.forEach((zone, index) => {
if (direction === "row") {
zone.size.width = `${100 / newZoneCount}%`;
} else {
zone.size.height = `${100 / newZoneCount}%`;
}
});
onUpdateProperty(layoutComponent.id, "zones", newZones);
}
}}
className="w-20 rounded border border-gray-300 px-2 py-1 text-sm"
/>
<span className="text-xs text-gray-500"></span>
</div>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> (px)</label>
<input
type="number"
min="0"
max="50"
value={layoutComponent.layoutConfig?.flexbox?.gap || 16}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.flexbox.gap", parseInt(e.target.value))
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
</div>
)}
{/* 분할 레이아웃 설정 */}
{layoutComponent.layoutType === "split" && (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div>
<label className="mb-1 block text-xs font-medium text-gray-700"> </label>
<select
value={layoutComponent.layoutConfig?.split?.direction || "horizontal"}
onChange={(e) => onUpdateProperty(layoutComponent.id, "layoutConfig.split.direction", e.target.value)}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value="horizontal"> </option>
<option value="vertical"> </option>
</select>
</div>
</div>
)}
{/* 카드 레이아웃 설정 */}
{layoutComponent.layoutType === "card-layout" && (
<div className="space-y-4">
<h4 className="text-sm font-medium text-gray-900"> </h4>
{/* 테이블 컬럼 매핑 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h5 className="text-xs font-medium text-gray-700"> </h5>
{currentTable && (
<span className="rounded bg-blue-50 px-2 py-1 text-xs text-blue-600">
: {currentTable.table_name}
</span>
)}
</div>
{/* 테이블이 선택되지 않은 경우 안내 */}
{!currentTable && (
<div className="rounded-lg bg-yellow-50 p-3 text-center">
<p className="text-sm text-yellow-700"> </p>
<p className="mt-1 text-xs text-yellow-600">
</p>
</div>
)}
{/* 테이블이 선택된 경우 컬럼 드롭다운 */}
{currentTable && (
<>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<select
value={layoutComponent.layoutConfig?.card?.columnMapping?.titleColumn || ""}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.titleColumn",
e.target.value,
)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value=""> </option>
{currentTable.columns?.map((column) => (
<option key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName} ({column.dataType})
</option>
))}
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<select
value={layoutComponent.layoutConfig?.card?.columnMapping?.subtitleColumn || ""}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.subtitleColumn",
e.target.value,
)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value=""> </option>
{currentTable.columns?.map((column) => (
<option key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName} ({column.dataType})
</option>
))}
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<select
value={layoutComponent.layoutConfig?.card?.columnMapping?.descriptionColumn || ""}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.descriptionColumn",
e.target.value,
)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value=""> </option>
{currentTable.columns?.map((column) => (
<option key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName} ({column.dataType})
</option>
))}
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<select
value={layoutComponent.layoutConfig?.card?.columnMapping?.imageColumn || ""}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.imageColumn",
e.target.value,
)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value=""> </option>
{currentTable.columns?.map((column) => (
<option key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName} ({column.dataType})
</option>
))}
</select>
</div>
{/* 동적 표시 컬럼 추가 */}
<div>
<div className="mb-2 flex items-center justify-between">
<label className="text-xs font-medium text-gray-600"> </label>
<button
type="button"
onClick={() => {
const currentColumns =
layoutComponent.layoutConfig?.card?.columnMapping?.displayColumns || [];
const newColumns = [...currentColumns, ""];
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.displayColumns",
newColumns,
);
}}
className="rounded bg-blue-500 px-2 py-1 text-xs text-white hover:bg-blue-600"
>
+
</button>
</div>
<div className="space-y-2">
{(layoutComponent.layoutConfig?.card?.columnMapping?.displayColumns || []).map(
(column, index) => (
<div key={index} className="flex items-center space-x-2">
<select
value={column}
onChange={(e) => {
const currentColumns = [
...(layoutComponent.layoutConfig?.card?.columnMapping?.displayColumns || []),
];
currentColumns[index] = e.target.value;
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.displayColumns",
currentColumns,
);
}}
className="flex-1 rounded border border-gray-300 px-2 py-1 text-sm"
>
<option value=""> </option>
{currentTable.columns?.map((col) => (
<option key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName} ({col.dataType})
</option>
))}
</select>
<button
type="button"
onClick={() => {
const currentColumns = [
...(layoutComponent.layoutConfig?.card?.columnMapping?.displayColumns || []),
];
currentColumns.splice(index, 1);
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.columnMapping.displayColumns",
currentColumns,
);
}}
className="rounded bg-red-500 px-2 py-1 text-xs text-white hover:bg-red-600"
>
</button>
</div>
),
)}
{(!layoutComponent.layoutConfig?.card?.columnMapping?.displayColumns ||
layoutComponent.layoutConfig.card.columnMapping.displayColumns.length === 0) && (
<div className="rounded border border-dashed border-gray-300 py-2 text-center text-xs text-gray-500">
"컬럼 추가"
</div>
)}
</div>
</div>
</>
)}
</div>
{/* 카드 스타일 설정 */}
<div className="space-y-3">
<h5 className="text-xs font-medium text-gray-700"> </h5>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<input
type="number"
min="1"
max="6"
value={layoutComponent.layoutConfig?.card?.cardsPerRow || 3}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.card.cardsPerRow", parseInt(e.target.value))
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> (px)</label>
<input
type="number"
min="0"
max="50"
value={layoutComponent.layoutConfig?.card?.cardSpacing || 16}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.card.cardSpacing", parseInt(e.target.value))
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="showTitle"
checked={layoutComponent.layoutConfig?.card?.cardStyle?.showTitle ?? true}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.card.cardStyle.showTitle", e.target.checked)
}
className="rounded border-gray-300"
/>
<label htmlFor="showTitle" className="text-xs text-gray-600">
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="showSubtitle"
checked={layoutComponent.layoutConfig?.card?.cardStyle?.showSubtitle ?? true}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.cardStyle.showSubtitle",
e.target.checked,
)
}
className="rounded border-gray-300"
/>
<label htmlFor="showSubtitle" className="text-xs text-gray-600">
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="showDescription"
checked={layoutComponent.layoutConfig?.card?.cardStyle?.showDescription ?? true}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.cardStyle.showDescription",
e.target.checked,
)
}
className="rounded border-gray-300"
/>
<label htmlFor="showDescription" className="text-xs text-gray-600">
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="showImage"
checked={layoutComponent.layoutConfig?.card?.cardStyle?.showImage ?? false}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, "layoutConfig.card.cardStyle.showImage", e.target.checked)
}
className="rounded border-gray-300"
/>
<label htmlFor="showImage" className="text-xs text-gray-600">
</label>
</div>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-600"> </label>
<input
type="number"
min="10"
max="500"
value={layoutComponent.layoutConfig?.card?.cardStyle?.maxDescriptionLength || 100}
onChange={(e) =>
onUpdateProperty(
layoutComponent.id,
"layoutConfig.card.cardStyle.maxDescriptionLength",
parseInt(e.target.value),
)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-sm"
/>
</div>
</div>
</div>
)}
{/* 존 목록 - 카드 레이아웃은 데이터 기반이므로 존 관리 불필요 */}
{layoutComponent.layoutType !== "card-layout" && (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900"> </h4>
<div className="space-y-2">
{layoutComponent.zones?.map((zone, index) => (
<div key={zone.id} className="rounded-lg bg-gray-50 p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-medium text-gray-700">{zone.name}</span>
<span className="text-xs text-gray-500">ID: {zone.id}</span>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="mb-1 block text-xs text-gray-600"></label>
<input
type="text"
value={zone.size?.width || "100%"}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, `zones.${index}.size.width`, e.target.value)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
placeholder="100%"
/>
</div>
<div>
<label className="mb-1 block text-xs text-gray-600"></label>
<input
type="text"
value={zone.size?.height || "auto"}
onChange={(e) =>
onUpdateProperty(layoutComponent.id, `zones.${index}.size.height`, e.target.value)
}
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
placeholder="auto"
/>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
};
// 웹타입별 상세 설정 렌더링 함수 - 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 업데이트:", {
widgetType: widget.widgetType,
oldConfig: currentConfig,
newConfig,
componentId: widget.id,
isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
});
// 강제 새 객체 생성으로 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>
);
}
} else {
console.log(`🎨 config_panel이 없음 - 기본 설정 표시`);
return (
<div className="py-8 text-center text-gray-500">
<br />
"{widget.widgetType}" .
</div>
);
}
};
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>
);
}
// 컴포넌트 타입별 설정 패널 렌더링
const renderComponentConfigPanel = () => {
console.log("🔍 renderComponentConfigPanel - selectedComponent:", selectedComponent);
if (!selectedComponent) {
console.error("❌ selectedComponent가 undefined입니다!");
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-red-400" />
<h3 className="mb-2 text-lg font-medium text-red-900"></h3>
<p className="text-sm text-red-500"> .</p>
</div>
);
}
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 <NewButtonConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "card":
return <CardConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "dashboard":
return <DashboardConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "stats":
case "stats-card":
return <StatsCardConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "progress":
case "progress-bar":
return <ProgressBarConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "chart":
case "chart-basic":
return <ChartConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "alert":
case "alert-info":
return <AlertConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
case "badge":
case "badge-status":
return <BadgeConfigPanel component={selectedComponent} onUpdateProperty={handleUpdateProperty} />;
default:
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"> "{componentType}" .</p>
</div>
);
}
};
// 새로운 컴포넌트 타입들에 대한 설정 패널 확인
const componentType = selectedComponent?.componentConfig?.type || selectedComponent?.type;
console.log("🔍 DetailSettingsPanel componentType 확인:", {
selectedComponentType: selectedComponent?.type,
componentConfigType: selectedComponent?.componentConfig?.type,
finalComponentType: componentType,
});
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);
console.log("🔍 hasNewConfigPanel:", hasNewConfigPanel);
if (hasNewConfigPanel) {
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">{componentType}</span>
</div>
</div>
{/* 설정 패널 영역 */}
<div className="flex-1 overflow-y-auto p-4">{renderComponentConfigPanel()}</div>
</div>
);
}
// 레이아웃 컴포넌트 처리
if (selectedComponent.type === "layout") {
return renderLayoutConfig(selectedComponent as LayoutComponent);
}
if (
selectedComponent.type !== "widget" &&
selectedComponent.type !== "file" &&
selectedComponent.type !== "button" &&
selectedComponent.type !== "component"
) {
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">
, , , , .
<br />
: {selectedComponent.type}
</p>
</div>
);
}
// 파일 컴포넌트인 경우 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">
<FileComponentConfigPanel
component={fileComponent}
onUpdateProperty={onUpdateProperty}
currentTable={currentTable}
currentTableName={currentTableName}
/>
</div>
</div>
);
}
// 레거시 버튼을 새로운 컴포넌트 시스템으로 강제 변환
if (selectedComponent.type === "button") {
console.log("🔄 레거시 버튼을 새로운 컴포넌트 시스템으로 변환:", selectedComponent);
// 레거시 버튼을 새로운 시스템으로 변환
const convertedComponent = {
...selectedComponent,
type: "component" as const,
componentConfig: {
type: "button-primary",
webType: "button",
...selectedComponent.componentConfig,
},
};
// 변환된 컴포넌트로 DB 업데이트
onUpdateProperty(selectedComponent.id, "type", "component");
onUpdateProperty(selectedComponent.id, "componentConfig", convertedComponent.componentConfig);
// 변환된 컴포넌트로 처리 계속
selectedComponent = convertedComponent;
}
// 새로운 컴포넌트 시스템 처리 (type: "component")
if (selectedComponent.type === "component") {
const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type;
const webType = selectedComponent.componentConfig?.webType;
console.log("🔧 새로운 컴포넌트 시스템 설정 패널:", { componentId, webType });
if (!componentId) {
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"> ID가 </h3>
<p className="text-sm text-gray-500">componentConfig.type이 .</p>
</div>
);
}
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">{componentId}</span>
</div>
{webType && (
<div className="mt-1 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">{webType}</span>
</div>
)}
{selectedComponent.columnName && (
<div className="mt-1 text-xs text-gray-500">: {selectedComponent.columnName}</div>
)}
</div>
{/* 컴포넌트 설정 패널 */}
<div className="flex-1 overflow-y-auto p-4">
<DynamicComponentConfigPanel
componentId={componentId}
config={(() => {
const config = selectedComponent.componentConfig || {};
console.log("🔍 DetailSettingsPanel에서 전달하는 config:", config);
console.log("🔍 selectedComponent 전체:", selectedComponent);
return config;
})()}
screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName}
tableColumns={(() => {
console.log("🔍 DetailSettingsPanel tableColumns 전달:", {
currentTable,
columns: currentTable?.columns,
columnsLength: currentTable?.columns?.length,
sampleColumn: currentTable?.columns?.[0],
deptCodeColumn: currentTable?.columns?.find((col) => col.columnName === "dept_code"),
});
return currentTable?.columns || [];
})()}
onChange={(newConfig) => {
console.log("🔧 컴포넌트 설정 변경:", newConfig);
// 개별 속성별로 업데이트하여 다른 속성과의 충돌 방지
Object.entries(newConfig).forEach(([key, value]) => {
onUpdateProperty(selectedComponent.id, `componentConfig.${key}`, value);
});
}}
/>
</div>
</div>
);
}
// 기존 위젯 시스템 처리 (type: "widget")
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;