드래그 앤 드롭 기능 개선 및 Unified 컴포넌트 매핑 추가: ScreenDesigner, TabsWidget, DynamicComponentRenderer에서 드래그 앤 드롭 시 컴포넌트의 위치와 크기를 최적화하고, Unified 컴포넌트에 대한 매핑 로직을 추가하여 사용자 경험을 향상시켰습니다. 또한, ButtonConfigPanel에서 컴포넌트가 없는 경우 방어 처리 로직을 추가하여 안정성을 높였습니다.
This commit is contained in:
@@ -2413,10 +2413,21 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
const dropX = (e.clientX - tabContentRect.left) / zoomLevel;
|
||||
const dropY = (e.clientY - tabContentRect.top) / zoomLevel;
|
||||
|
||||
// 새 컴포넌트 생성
|
||||
// 새 컴포넌트 생성 - 드롭된 컴포넌트의 id를 그대로 사용
|
||||
// component.id는 ComponentDefinition의 id (예: "v2-table-list", "v2-button-primary")
|
||||
const componentType = component.id || component.componentType || "v2-text-display";
|
||||
|
||||
console.log("🎯 탭에 컴포넌트 드롭:", {
|
||||
componentId: component.id,
|
||||
componentType: componentType,
|
||||
componentName: component.name,
|
||||
defaultConfig: component.defaultConfig,
|
||||
defaultSize: component.defaultSize,
|
||||
});
|
||||
|
||||
const newTabComponent = {
|
||||
id: `tab_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
componentType: component.id || component.componentType || "text-display",
|
||||
componentType: componentType,
|
||||
label: component.name || component.label || "새 컴포넌트",
|
||||
position: { x: Math.max(0, dropX), y: Math.max(0, dropY) },
|
||||
size: component.defaultSize || { width: 200, height: 100 },
|
||||
@@ -2858,16 +2869,55 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
const dropX = (e.clientX - tabContentRect.left) / zoomLevel;
|
||||
const dropY = (e.clientY - tabContentRect.top) / zoomLevel;
|
||||
|
||||
// 새 컴포넌트 생성 (컬럼 기반)
|
||||
// 🆕 Unified 컴포넌트 매핑 사용 (일반 캔버스와 동일)
|
||||
const unifiedMapping = createUnifiedConfigFromColumn({
|
||||
widgetType: column.widgetType,
|
||||
columnName: column.columnName,
|
||||
columnLabel: column.columnLabel,
|
||||
codeCategory: column.codeCategory,
|
||||
inputType: column.inputType,
|
||||
required: column.required,
|
||||
detailSettings: column.detailSettings,
|
||||
referenceTable: column.referenceTable,
|
||||
referenceColumn: column.referenceColumn,
|
||||
displayColumn: column.displayColumn,
|
||||
});
|
||||
|
||||
// 웹타입별 기본 크기 계산
|
||||
const getTabComponentSize = (widgetType: string) => {
|
||||
const sizeMap: Record<string, { width: number; height: number }> = {
|
||||
text: { width: 200, height: 36 },
|
||||
number: { width: 150, height: 36 },
|
||||
decimal: { width: 150, height: 36 },
|
||||
date: { width: 180, height: 36 },
|
||||
datetime: { width: 200, height: 36 },
|
||||
select: { width: 200, height: 36 },
|
||||
category: { width: 200, height: 36 },
|
||||
code: { width: 200, height: 36 },
|
||||
entity: { width: 220, height: 36 },
|
||||
boolean: { width: 120, height: 36 },
|
||||
checkbox: { width: 120, height: 36 },
|
||||
textarea: { width: 300, height: 100 },
|
||||
file: { width: 250, height: 80 },
|
||||
};
|
||||
return sizeMap[widgetType] || { width: 200, height: 36 };
|
||||
};
|
||||
|
||||
const componentSize = getTabComponentSize(column.widgetType);
|
||||
|
||||
const newTabComponent = {
|
||||
id: `tab_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
componentType: column.widgetType || "unified-input",
|
||||
componentType: unifiedMapping.componentType, // unified-input, unified-select 등
|
||||
label: column.columnLabel || column.columnName,
|
||||
position: { x: Math.max(0, dropX), y: Math.max(0, dropY) },
|
||||
size: { width: 200, height: 60 },
|
||||
size: componentSize,
|
||||
inputType: column.inputType || column.widgetType, // 🆕 inputType 저장 (설정 패널용)
|
||||
widgetType: column.widgetType, // 🆕 widgetType 저장
|
||||
componentConfig: {
|
||||
...unifiedMapping.componentConfig, // Unified 컴포넌트 기본 설정
|
||||
columnName: column.columnName,
|
||||
tableName: column.tableName,
|
||||
inputType: column.inputType || column.widgetType, // 🆕 componentConfig에도 저장
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4888,6 +4938,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
size: tabComp.size,
|
||||
componentConfig: tabComp.componentConfig || {},
|
||||
style: tabComp.style || {},
|
||||
inputType: tabComp.inputType || tabComp.componentConfig?.inputType, // 🆕 inputType 추가
|
||||
widgetType: tabComp.widgetType || tabComp.componentConfig?.widgetType, // 🆕 widgetType 추가
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -4895,60 +4947,68 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
componentId={tabComp.componentType}
|
||||
component={componentForConfig}
|
||||
config={tabComp.componentConfig || {}}
|
||||
screenTableName={selectedScreen?.tableName}
|
||||
tableColumns={tables.length > 0 ? tables[0].columns : []}
|
||||
menuObjid={selectedScreen?.menuObjid}
|
||||
currentComponent={componentForConfig}
|
||||
onChange={(newConfig: any) => {
|
||||
// componentConfig 전체 업데이트
|
||||
// componentConfig 전체 업데이트 - 함수형 업데이트로 클로저 문제 해결
|
||||
const { tabsComponentId, tabId, componentId } = selectedTabComponentInfo;
|
||||
const tabsComponent = layout.components.find((c) => c.id === tabsComponentId);
|
||||
if (!tabsComponent) return;
|
||||
|
||||
setLayout((prevLayout) => {
|
||||
const tabsComponent = prevLayout.components.find((c) => c.id === tabsComponentId);
|
||||
if (!tabsComponent) return prevLayout;
|
||||
|
||||
const currentConfig = (tabsComponent as any).componentConfig || {};
|
||||
const tabs = currentConfig.tabs || [];
|
||||
const currentConfig = (tabsComponent as any).componentConfig || {};
|
||||
const tabs = currentConfig.tabs || [];
|
||||
|
||||
const updatedTabs = tabs.map((tab: any) => {
|
||||
if (tab.id === tabId) {
|
||||
return {
|
||||
...tab,
|
||||
components: (tab.components || []).map((comp: any) =>
|
||||
comp.id === componentId
|
||||
? { ...comp, componentConfig: newConfig }
|
||||
: comp
|
||||
),
|
||||
};
|
||||
}
|
||||
return tab;
|
||||
});
|
||||
|
||||
const updatedComponent = {
|
||||
...tabsComponent,
|
||||
componentConfig: {
|
||||
...currentConfig,
|
||||
tabs: updatedTabs,
|
||||
},
|
||||
};
|
||||
|
||||
const newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) =>
|
||||
c.id === tabsComponentId ? updatedComponent : c
|
||||
),
|
||||
};
|
||||
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
|
||||
// 선택된 컴포넌트 정보 업데이트
|
||||
const updatedComp = updatedTabs
|
||||
.find((t: any) => t.id === tabId)
|
||||
?.components?.find((c: any) => c.id === componentId);
|
||||
if (updatedComp) {
|
||||
setSelectedTabComponentInfo({
|
||||
...selectedTabComponentInfo,
|
||||
component: updatedComp,
|
||||
const updatedTabs = tabs.map((tab: any) => {
|
||||
if (tab.id === tabId) {
|
||||
return {
|
||||
...tab,
|
||||
components: (tab.components || []).map((comp: any) =>
|
||||
comp.id === componentId
|
||||
? { ...comp, componentConfig: newConfig }
|
||||
: comp
|
||||
),
|
||||
};
|
||||
}
|
||||
return tab;
|
||||
});
|
||||
}
|
||||
|
||||
const updatedComponent = {
|
||||
...tabsComponent,
|
||||
componentConfig: {
|
||||
...currentConfig,
|
||||
tabs: updatedTabs,
|
||||
},
|
||||
};
|
||||
|
||||
const newLayout = {
|
||||
...prevLayout,
|
||||
components: prevLayout.components.map((c) =>
|
||||
c.id === tabsComponentId ? updatedComponent : c
|
||||
),
|
||||
};
|
||||
|
||||
// 선택된 컴포넌트 정보 업데이트
|
||||
const updatedComp = updatedTabs
|
||||
.find((t: any) => t.id === tabId)
|
||||
?.components?.find((c: any) => c.id === componentId);
|
||||
if (updatedComp) {
|
||||
setSelectedTabComponentInfo((prev) =>
|
||||
prev ? { ...prev, component: updatedComp } : null
|
||||
);
|
||||
}
|
||||
|
||||
return newLayout;
|
||||
});
|
||||
}}
|
||||
screenTableName={selectedScreen?.tableName}
|
||||
tableColumns={tables.length > 0 ? tables[0].columns : []}
|
||||
tables={tables}
|
||||
allComponents={layout.components}
|
||||
menuObjid={selectedScreen?.menuObjid}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
@@ -49,6 +49,15 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
currentTableName, // 현재 화면의 테이블명
|
||||
currentScreenCompanyCode, // 현재 편집 중인 화면의 회사 코드
|
||||
}) => {
|
||||
// 🔧 component가 없는 경우 방어 처리
|
||||
if (!component) {
|
||||
return (
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
컴포넌트 정보를 불러올 수 없습니다.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔧 component에서 직접 읽기 (useMemo 제거)
|
||||
const config = component.componentConfig || {};
|
||||
const currentAction = component.componentConfig?.action || {};
|
||||
|
||||
@@ -137,8 +137,24 @@ export function TabsWidget({
|
||||
);
|
||||
}
|
||||
|
||||
// 컴포넌트들의 최대 위치를 계산하여 스크롤 가능한 영역 확보
|
||||
const maxBottom = Math.max(
|
||||
...components.map((c) => (c.position?.y || 0) + (c.size?.height || 100)),
|
||||
300 // 최소 높이
|
||||
);
|
||||
const maxRight = Math.max(
|
||||
...components.map((c) => (c.position?.x || 0) + (c.size?.width || 200)),
|
||||
400 // 최소 너비
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
minHeight: maxBottom + 20,
|
||||
minWidth: maxRight + 20,
|
||||
}}
|
||||
>
|
||||
{components.map((comp: TabInlineComponent) => {
|
||||
const isSelected = selectedComponentId === comp.id;
|
||||
|
||||
@@ -228,7 +244,7 @@ export function TabsWidget({
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<div className="relative flex-1 overflow-hidden">
|
||||
<div className="relative flex-1 overflow-auto">
|
||||
{visibleTabs.map((tab) => {
|
||||
const shouldRender = mountedTabs.has(tab.id);
|
||||
const isActive = selectedTab === tab.id;
|
||||
@@ -238,7 +254,7 @@ export function TabsWidget({
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
forceMount
|
||||
className={cn("h-full", !isActive && "hidden")}
|
||||
className={cn("h-full overflow-auto", !isActive && "hidden")}
|
||||
>
|
||||
{shouldRender && renderTabComponents(tab)}
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user