feat: enhance split panel and tab component interactions
- Improved the drop handling logic for split panels and tabs, prioritizing the innermost container during drag-and-drop operations. - Added internal state management for selected components within split panels to enhance user experience. - Updated the rendering logic to ensure proper data attributes are set for design mode, facilitating better component identification. - Enhanced the dynamic component rendering to support updates and selections for nested components within tabs and split panels. Made-with: Cursor
This commit is contained in:
parent
b1e50f2e0a
commit
014979bebf
|
|
@ -2861,9 +2861,190 @@ export default function ScreenDesigner({
|
|||
}
|
||||
}
|
||||
|
||||
// 🎯 탭 컨테이너 내부 드롭 처리 (중첩 구조 지원)
|
||||
// 🎯 컨테이너 드롭 우선순위: 가장 안쪽(innermost) 컨테이너 우선
|
||||
// 분할패널과 탭 둘 다 감지될 경우, DOM 트리에서 더 가까운 쪽을 우선 처리
|
||||
const tabsContainer = dropTarget.closest('[data-tabs-container="true"]');
|
||||
if (tabsContainer) {
|
||||
const splitPanelContainer = dropTarget.closest('[data-split-panel-container="true"]');
|
||||
|
||||
// 분할패널이 탭보다 안쪽에 있으면 분할패널 우선 처리
|
||||
const splitPanelFirst =
|
||||
splitPanelContainer &&
|
||||
(!tabsContainer || tabsContainer.contains(splitPanelContainer));
|
||||
|
||||
if (splitPanelFirst && splitPanelContainer) {
|
||||
const containerId = splitPanelContainer.getAttribute("data-component-id");
|
||||
const panelSide = splitPanelContainer.getAttribute("data-panel-side");
|
||||
if (containerId && panelSide) {
|
||||
// 분할 패널을 최상위 또는 중첩(탭 안)에서 찾기
|
||||
let targetComponent: any = layout.components.find((c) => c.id === containerId);
|
||||
let parentTabsId: string | null = null;
|
||||
let parentTabId: string | null = null;
|
||||
let parentSplitId: string | null = null;
|
||||
let parentSplitSide: string | null = null;
|
||||
|
||||
if (!targetComponent) {
|
||||
// 탭 안에 중첩된 분할패널 찾기
|
||||
// top-level: overrides.type / overrides.tabs
|
||||
// nested: componentType / componentConfig.tabs
|
||||
for (const comp of layout.components) {
|
||||
const compType = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
||||
const compConfig = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
||||
|
||||
if (compType === "tabs-widget" || compType === "v2-tabs-widget") {
|
||||
const tabs = compConfig.tabs || [];
|
||||
for (const tab of tabs) {
|
||||
const found = (tab.components || []).find((c: any) => c.id === containerId);
|
||||
if (found) {
|
||||
targetComponent = found;
|
||||
parentTabsId = comp.id;
|
||||
parentTabId = tab.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
|
||||
if (compType === "split-panel-layout" || compType === "v2-split-panel-layout") {
|
||||
for (const side of ["leftPanel", "rightPanel"] as const) {
|
||||
const panelComps = compConfig[side]?.components || [];
|
||||
for (const pc of panelComps) {
|
||||
const pct = pc.componentType || pc.overrides?.type;
|
||||
if (pct === "tabs-widget" || pct === "v2-tabs-widget") {
|
||||
const tabs = (pc.componentConfig || pc.overrides || {}).tabs || [];
|
||||
for (const tab of tabs) {
|
||||
const found = (tab.components || []).find((c: any) => c.id === containerId);
|
||||
if (found) {
|
||||
targetComponent = found;
|
||||
parentSplitId = comp.id;
|
||||
parentSplitSide = side === "leftPanel" ? "left" : "right";
|
||||
parentTabsId = pc.id;
|
||||
parentTabId = tab.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const compType = (targetComponent as any)?.componentType;
|
||||
if (targetComponent && (compType === "split-panel-layout" || compType === "v2-split-panel-layout")) {
|
||||
const currentConfig = (targetComponent as any).componentConfig || {};
|
||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||
const panelConfig = currentConfig[panelKey] || {};
|
||||
const currentComponents = panelConfig.components || [];
|
||||
|
||||
const panelRect = splitPanelContainer.getBoundingClientRect();
|
||||
const cs1 = window.getComputedStyle(splitPanelContainer);
|
||||
const dropX = (e.clientX - panelRect.left - (parseFloat(cs1.paddingLeft) || 0)) / zoomLevel;
|
||||
const dropY = (e.clientY - panelRect.top - (parseFloat(cs1.paddingTop) || 0)) / zoomLevel;
|
||||
|
||||
const componentType = component.id || component.componentType || "v2-text-display";
|
||||
|
||||
const newPanelComponent = {
|
||||
id: `panel_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
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 },
|
||||
componentConfig: component.defaultConfig || {},
|
||||
};
|
||||
|
||||
const updatedPanelConfig = {
|
||||
...panelConfig,
|
||||
components: [...currentComponents, newPanelComponent],
|
||||
};
|
||||
|
||||
const updatedSplitPanel = {
|
||||
...targetComponent,
|
||||
componentConfig: {
|
||||
...currentConfig,
|
||||
[panelKey]: updatedPanelConfig,
|
||||
},
|
||||
};
|
||||
|
||||
let newLayout;
|
||||
if (parentTabsId && parentTabId) {
|
||||
// 중첩: (최상위 분할패널 →) 탭 → 분할패널
|
||||
const updateTabsComponent = (tabsComp: any) => {
|
||||
const ck = tabsComp.componentConfig ? "componentConfig" : "overrides";
|
||||
const cfg = tabsComp[ck] || {};
|
||||
const tabs = cfg.tabs || [];
|
||||
return {
|
||||
...tabsComp,
|
||||
[ck]: {
|
||||
...cfg,
|
||||
tabs: tabs.map((tab: any) =>
|
||||
tab.id === parentTabId
|
||||
? {
|
||||
...tab,
|
||||
components: (tab.components || []).map((c: any) =>
|
||||
c.id === containerId ? updatedSplitPanel : c,
|
||||
),
|
||||
}
|
||||
: tab,
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (parentSplitId && parentSplitSide) {
|
||||
// 최상위 분할패널 → 탭 → 분할패널
|
||||
const pKey = parentSplitSide === "left" ? "leftPanel" : "rightPanel";
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => {
|
||||
if (c.id === parentSplitId) {
|
||||
const sc = (c as any).componentConfig || {};
|
||||
return {
|
||||
...c,
|
||||
componentConfig: {
|
||||
...sc,
|
||||
[pKey]: {
|
||||
...sc[pKey],
|
||||
components: (sc[pKey]?.components || []).map((pc: any) =>
|
||||
pc.id === parentTabsId ? updateTabsComponent(pc) : pc,
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return c;
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
// 최상위 탭 → 분할패널
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) =>
|
||||
c.id === parentTabsId ? updateTabsComponent(c) : c,
|
||||
),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 최상위 분할패널
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => (c.id === containerId ? updatedSplitPanel : c)),
|
||||
};
|
||||
}
|
||||
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
toast.success(`컴포넌트가 ${panelSide === "left" ? "좌측" : "우측"} 패널에 추가되었습니다`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabsContainer && !splitPanelFirst) {
|
||||
const containerId = tabsContainer.getAttribute("data-component-id");
|
||||
const activeTabId = tabsContainer.getAttribute("data-active-tab-id");
|
||||
if (containerId && activeTabId) {
|
||||
|
|
@ -3004,69 +3185,6 @@ export default function ScreenDesigner({
|
|||
}
|
||||
}
|
||||
|
||||
// 🎯 분할 패널 커스텀 모드 컨테이너 내부 드롭 처리
|
||||
const splitPanelContainer = dropTarget.closest('[data-split-panel-container="true"]');
|
||||
if (splitPanelContainer) {
|
||||
const containerId = splitPanelContainer.getAttribute("data-component-id");
|
||||
const panelSide = splitPanelContainer.getAttribute("data-panel-side"); // "left" or "right"
|
||||
if (containerId && panelSide) {
|
||||
const targetComponent = layout.components.find((c) => c.id === containerId);
|
||||
const compType = (targetComponent as any)?.componentType;
|
||||
if (targetComponent && (compType === "split-panel-layout" || compType === "v2-split-panel-layout")) {
|
||||
const currentConfig = (targetComponent as any).componentConfig || {};
|
||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||
const panelConfig = currentConfig[panelKey] || {};
|
||||
const currentComponents = panelConfig.components || [];
|
||||
|
||||
// 드롭 위치 계산
|
||||
const panelRect = splitPanelContainer.getBoundingClientRect();
|
||||
const dropX = (e.clientX - panelRect.left) / zoomLevel;
|
||||
const dropY = (e.clientY - panelRect.top) / zoomLevel;
|
||||
|
||||
// 새 컴포넌트 생성
|
||||
const componentType = component.id || component.componentType || "v2-text-display";
|
||||
|
||||
console.log("🎯 분할 패널에 컴포넌트 드롭:", {
|
||||
componentId: component.id,
|
||||
componentType: componentType,
|
||||
panelSide: panelSide,
|
||||
dropPosition: { x: dropX, y: dropY },
|
||||
});
|
||||
|
||||
const newPanelComponent = {
|
||||
id: `panel_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
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 },
|
||||
componentConfig: component.defaultConfig || {},
|
||||
};
|
||||
|
||||
const updatedPanelConfig = {
|
||||
...panelConfig,
|
||||
components: [...currentComponents, newPanelComponent],
|
||||
};
|
||||
|
||||
const updatedComponent = {
|
||||
...targetComponent,
|
||||
componentConfig: {
|
||||
...currentConfig,
|
||||
[panelKey]: updatedPanelConfig,
|
||||
},
|
||||
};
|
||||
|
||||
const newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => (c.id === containerId ? updatedComponent : c)),
|
||||
};
|
||||
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
toast.success(`컴포넌트가 ${panelSide === "left" ? "좌측" : "우측"} 패널에 추가되었습니다`);
|
||||
return; // 분할 패널 처리 완료
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
|
@ -3378,15 +3496,12 @@ export default function ScreenDesigner({
|
|||
e.preventDefault();
|
||||
|
||||
const dragData = e.dataTransfer.getData("application/json");
|
||||
// console.log("🎯 드롭 이벤트:", { dragData });
|
||||
if (!dragData) {
|
||||
// console.log("❌ 드래그 데이터가 없습니다");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(dragData);
|
||||
// console.log("📋 파싱된 데이터:", parsedData);
|
||||
|
||||
// 템플릿 드래그인 경우
|
||||
if (parsedData.type === "template") {
|
||||
|
|
@ -3480,9 +3595,225 @@ export default function ScreenDesigner({
|
|||
}
|
||||
}
|
||||
|
||||
// 🎯 탭 컨테이너 내부에 컬럼 드롭 시 처리 (중첩 구조 지원)
|
||||
// 🎯 컨테이너 감지: innermost 우선 (분할패널 > 탭)
|
||||
const tabsContainer = dropTarget.closest('[data-tabs-container="true"]');
|
||||
if (tabsContainer && type === "column" && column) {
|
||||
const splitPanelContainer = dropTarget.closest('[data-split-panel-container="true"]');
|
||||
|
||||
// 분할패널이 탭 안에 있으면 분할패널이 innermost → 분할패널 우선
|
||||
const splitPanelFirst =
|
||||
splitPanelContainer &&
|
||||
(!tabsContainer || tabsContainer.contains(splitPanelContainer));
|
||||
|
||||
// 🎯 분할패널 커스텀 모드 컨테이너 내부에 컬럼 드롭 시 처리 (우선 처리)
|
||||
if (splitPanelFirst && splitPanelContainer && type === "column" && column) {
|
||||
const containerId = splitPanelContainer.getAttribute("data-component-id");
|
||||
let panelSide = splitPanelContainer.getAttribute("data-panel-side");
|
||||
|
||||
// panelSide가 없으면 드롭 좌표와 splitRatio로 좌/우 판별
|
||||
if (!panelSide) {
|
||||
const splitRatio = parseInt(splitPanelContainer.getAttribute("data-split-ratio") || "40", 10);
|
||||
const containerRect = splitPanelContainer.getBoundingClientRect();
|
||||
const relativeX = e.clientX - containerRect.left;
|
||||
const splitPoint = containerRect.width * (splitRatio / 100);
|
||||
panelSide = relativeX < splitPoint ? "left" : "right";
|
||||
}
|
||||
|
||||
if (containerId && panelSide) {
|
||||
// 최상위에서 찾기
|
||||
let targetComponent: any = layout.components.find((c) => c.id === containerId);
|
||||
let parentTabsId: string | null = null;
|
||||
let parentTabId: string | null = null;
|
||||
let parentSplitId: string | null = null;
|
||||
let parentSplitSide: string | null = null;
|
||||
|
||||
if (!targetComponent) {
|
||||
// 탭 안 중첩 분할패널 찾기
|
||||
// top-level 컴포넌트: overrides.type / overrides.tabs
|
||||
// nested 컴포넌트: componentType / componentConfig.tabs
|
||||
for (const comp of layout.components) {
|
||||
const ct = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
||||
const compConfig = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
||||
|
||||
if (ct === "tabs-widget" || ct === "v2-tabs-widget") {
|
||||
const tabs = compConfig.tabs || [];
|
||||
for (const tab of tabs) {
|
||||
const found = (tab.components || []).find((c: any) => c.id === containerId);
|
||||
if (found) {
|
||||
targetComponent = found;
|
||||
parentTabsId = comp.id;
|
||||
parentTabId = tab.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
// 분할패널 → 탭 → 분할패널 중첩
|
||||
if (ct === "split-panel-layout" || ct === "v2-split-panel-layout") {
|
||||
for (const side of ["leftPanel", "rightPanel"] as const) {
|
||||
const panelComps = compConfig[side]?.components || [];
|
||||
for (const pc of panelComps) {
|
||||
const pct = pc.componentType || pc.overrides?.type;
|
||||
if (pct === "tabs-widget" || pct === "v2-tabs-widget") {
|
||||
const tabs = (pc.componentConfig || pc.overrides || {}).tabs || [];
|
||||
for (const tab of tabs) {
|
||||
const found = (tab.components || []).find((c: any) => c.id === containerId);
|
||||
if (found) {
|
||||
targetComponent = found;
|
||||
parentSplitId = comp.id;
|
||||
parentSplitSide = side === "leftPanel" ? "left" : "right";
|
||||
parentTabsId = pc.id;
|
||||
parentTabId = tab.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
if (targetComponent) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const compType = (targetComponent as any)?.componentType;
|
||||
if (targetComponent && (compType === "split-panel-layout" || compType === "v2-split-panel-layout")) {
|
||||
const currentConfig = (targetComponent as any).componentConfig || {};
|
||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||
const panelConfig = currentConfig[panelKey] || {};
|
||||
const currentComponents = panelConfig.components || [];
|
||||
|
||||
const panelRect = splitPanelContainer.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(splitPanelContainer);
|
||||
const padLeft = parseFloat(computedStyle.paddingLeft) || 0;
|
||||
const padTop = parseFloat(computedStyle.paddingTop) || 0;
|
||||
const dropX = (e.clientX - panelRect.left - padLeft) / zoomLevel;
|
||||
const dropY = (e.clientY - panelRect.top - padTop) / zoomLevel;
|
||||
|
||||
const v2Mapping = createV2ConfigFromColumn({
|
||||
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 newPanelComponent = {
|
||||
id: `panel_comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
componentType: v2Mapping.componentType,
|
||||
label: column.columnLabel || column.columnName,
|
||||
position: { x: Math.max(0, dropX), y: Math.max(0, dropY) },
|
||||
size: { width: 200, height: 36 },
|
||||
inputType: column.inputType || column.widgetType,
|
||||
widgetType: column.widgetType,
|
||||
componentConfig: {
|
||||
...v2Mapping.componentConfig,
|
||||
columnName: column.columnName,
|
||||
tableName: column.tableName,
|
||||
inputType: column.inputType || column.widgetType,
|
||||
},
|
||||
};
|
||||
|
||||
const updatedSplitPanel = {
|
||||
...targetComponent,
|
||||
componentConfig: {
|
||||
...currentConfig,
|
||||
[panelKey]: {
|
||||
...panelConfig,
|
||||
displayMode: "custom",
|
||||
components: [...currentComponents, newPanelComponent],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let newLayout;
|
||||
|
||||
if (parentSplitId && parentSplitSide && parentTabsId && parentTabId) {
|
||||
// 분할패널 → 탭 → 분할패널 3중 중첩
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => {
|
||||
if (c.id !== parentSplitId) return c;
|
||||
const sc = (c as any).componentConfig || {};
|
||||
const pk = parentSplitSide === "left" ? "leftPanel" : "rightPanel";
|
||||
return {
|
||||
...c,
|
||||
componentConfig: {
|
||||
...sc,
|
||||
[pk]: {
|
||||
...sc[pk],
|
||||
components: (sc[pk]?.components || []).map((pc: any) => {
|
||||
if (pc.id !== parentTabsId) return pc;
|
||||
return {
|
||||
...pc,
|
||||
componentConfig: {
|
||||
...pc.componentConfig,
|
||||
tabs: (pc.componentConfig?.tabs || []).map((tab: any) => {
|
||||
if (tab.id !== parentTabId) return tab;
|
||||
return {
|
||||
...tab,
|
||||
components: (tab.components || []).map((tc: any) =>
|
||||
tc.id === containerId ? updatedSplitPanel : tc,
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
} else if (parentTabsId && parentTabId) {
|
||||
// 탭 → 분할패널 2중 중첩
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => {
|
||||
if (c.id !== parentTabsId) return c;
|
||||
// top-level은 overrides, nested는 componentConfig
|
||||
const configKey = (c as any).componentConfig ? "componentConfig" : "overrides";
|
||||
const tabsConfig = (c as any)[configKey] || {};
|
||||
return {
|
||||
...c,
|
||||
[configKey]: {
|
||||
...tabsConfig,
|
||||
tabs: (tabsConfig.tabs || []).map((tab: any) => {
|
||||
if (tab.id !== parentTabId) return tab;
|
||||
return {
|
||||
...tab,
|
||||
components: (tab.components || []).map((tc: any) =>
|
||||
tc.id === containerId ? updatedSplitPanel : tc,
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
// 최상위 분할패널
|
||||
newLayout = {
|
||||
...layout,
|
||||
components: layout.components.map((c) => (c.id === containerId ? updatedSplitPanel : c)),
|
||||
};
|
||||
}
|
||||
|
||||
toast.success("컬럼이 분할패널에 추가되었습니다");
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 탭 컨테이너 내부에 컬럼 드롭 시 처리 (중첩 구조 지원)
|
||||
if (tabsContainer && !splitPanelFirst && type === "column" && column) {
|
||||
const containerId = tabsContainer.getAttribute("data-component-id");
|
||||
const activeTabId = tabsContainer.getAttribute("data-active-tab-id");
|
||||
if (containerId && activeTabId) {
|
||||
|
|
@ -3648,9 +3979,8 @@ export default function ScreenDesigner({
|
|||
}
|
||||
}
|
||||
|
||||
// 🎯 분할 패널 커스텀 모드 컨테이너 내부에 컬럼 드롭 시 처리
|
||||
const splitPanelContainer = dropTarget.closest('[data-split-panel-container="true"]');
|
||||
if (splitPanelContainer && type === "column" && column) {
|
||||
// 🎯 분할 패널 커스텀 모드 (탭 밖 최상위) 컬럼 드롭 처리
|
||||
if (splitPanelContainer && !splitPanelFirst && type === "column" && column) {
|
||||
const containerId = splitPanelContainer.getAttribute("data-component-id");
|
||||
const panelSide = splitPanelContainer.getAttribute("data-panel-side"); // "left" or "right"
|
||||
if (containerId && panelSide) {
|
||||
|
|
@ -3662,12 +3992,11 @@ export default function ScreenDesigner({
|
|||
const panelConfig = currentConfig[panelKey] || {};
|
||||
const currentComponents = panelConfig.components || [];
|
||||
|
||||
// 드롭 위치 계산
|
||||
const panelRect = splitPanelContainer.getBoundingClientRect();
|
||||
const dropX = (e.clientX - panelRect.left) / zoomLevel;
|
||||
const dropY = (e.clientY - panelRect.top) / zoomLevel;
|
||||
const cs2 = window.getComputedStyle(splitPanelContainer);
|
||||
const dropX = (e.clientX - panelRect.left - (parseFloat(cs2.paddingLeft) || 0)) / zoomLevel;
|
||||
const dropY = (e.clientY - panelRect.top - (parseFloat(cs2.paddingTop) || 0)) / zoomLevel;
|
||||
|
||||
// V2 컴포넌트 매핑 사용
|
||||
const v2Mapping = createV2ConfigFromColumn({
|
||||
widgetType: column.widgetType,
|
||||
columnName: column.columnName,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import {
|
|||
GeneratedLocation,
|
||||
RackStructureContext,
|
||||
} from "./types";
|
||||
import { applyLocationPattern, DEFAULT_CODE_PATTERN, DEFAULT_NAME_PATTERN } from "./patternUtils";
|
||||
|
||||
// 기존 위치 데이터 타입
|
||||
interface ExistingLocation {
|
||||
|
|
@ -513,27 +512,23 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
|||
return { totalLocations, totalRows, maxLevel };
|
||||
}, [conditions]);
|
||||
|
||||
// 위치 코드 생성 (패턴 기반)
|
||||
// 위치 코드 생성
|
||||
const generateLocationCode = useCallback(
|
||||
(row: number, level: number): { code: string; name: string } => {
|
||||
const vars = {
|
||||
warehouse: context?.warehouseCode || "WH001",
|
||||
warehouseName: context?.warehouseName || "",
|
||||
floor: context?.floor || "1",
|
||||
zone: context?.zone || "A",
|
||||
row,
|
||||
level,
|
||||
};
|
||||
const warehouseCode = context?.warehouseCode || "WH001";
|
||||
const floor = context?.floor || "1";
|
||||
const zone = context?.zone || "A";
|
||||
|
||||
const codePattern = config.codePattern || DEFAULT_CODE_PATTERN;
|
||||
const namePattern = config.namePattern || DEFAULT_NAME_PATTERN;
|
||||
// 코드 생성 (예: WH001-1층D구역-01-1)
|
||||
const code = `${warehouseCode}-${floor}${zone}-${row.toString().padStart(2, "0")}-${level}`;
|
||||
|
||||
return {
|
||||
code: applyLocationPattern(codePattern, vars),
|
||||
name: applyLocationPattern(namePattern, vars),
|
||||
};
|
||||
// 이름 생성 - zone에 이미 "구역"이 포함되어 있으면 그대로 사용
|
||||
const zoneName = zone.includes("구역") ? zone : `${zone}구역`;
|
||||
const name = `${zoneName}-${row.toString().padStart(2, "0")}열-${level}단`;
|
||||
|
||||
return { code, name };
|
||||
},
|
||||
[context, config.codePattern, config.namePattern],
|
||||
[context],
|
||||
);
|
||||
|
||||
// 미리보기 생성
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
|
@ -12,47 +12,6 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { RackStructureComponentConfig, FieldMapping } from "./types";
|
||||
import { applyLocationPattern, DEFAULT_CODE_PATTERN, DEFAULT_NAME_PATTERN, PATTERN_VARIABLES } from "./patternUtils";
|
||||
|
||||
// 패턴 미리보기 서브 컴포넌트
|
||||
const PatternPreview: React.FC<{
|
||||
codePattern?: string;
|
||||
namePattern?: string;
|
||||
}> = ({ codePattern, namePattern }) => {
|
||||
const sampleVars = {
|
||||
warehouse: "WH002",
|
||||
warehouseName: "2창고",
|
||||
floor: "2층",
|
||||
zone: "A구역",
|
||||
row: 1,
|
||||
level: 3,
|
||||
};
|
||||
|
||||
const previewCode = useMemo(
|
||||
() => applyLocationPattern(codePattern || DEFAULT_CODE_PATTERN, sampleVars),
|
||||
[codePattern],
|
||||
);
|
||||
const previewName = useMemo(
|
||||
() => applyLocationPattern(namePattern || DEFAULT_NAME_PATTERN, sampleVars),
|
||||
[namePattern],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-primary/20 bg-primary/5 p-2.5">
|
||||
<div className="mb-1.5 text-[10px] font-medium text-primary">미리보기 (2창고 / 2층 / A구역 / 1열 / 3단)</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="w-14 shrink-0 text-muted-foreground">위치코드:</span>
|
||||
<code className="rounded bg-background px-1.5 py-0.5 font-mono text-foreground">{previewCode}</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="w-14 shrink-0 text-muted-foreground">위치명:</span>
|
||||
<code className="rounded bg-background px-1.5 py-0.5 font-mono text-foreground">{previewName}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RackStructureConfigPanelProps {
|
||||
config: RackStructureComponentConfig;
|
||||
|
|
@ -246,61 +205,6 @@ export const RackStructureConfigPanel: React.FC<RackStructureConfigPanelProps> =
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 위치코드 패턴 설정 */}
|
||||
<div className="space-y-3 border-t pt-3">
|
||||
<div className="text-sm font-medium text-foreground">위치코드/위치명 패턴</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
변수를 조합하여 위치코드와 위치명 생성 규칙을 설정하세요
|
||||
</p>
|
||||
|
||||
{/* 위치코드 패턴 */}
|
||||
<div>
|
||||
<Label className="text-xs">위치코드 패턴</Label>
|
||||
<Input
|
||||
value={config.codePattern || ""}
|
||||
onChange={(e) => handleChange("codePattern", e.target.value || undefined)}
|
||||
placeholder="{warehouse}-{floor}{zone}-{row:02}-{level}"
|
||||
className="h-8 font-mono text-xs"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
||||
비워두면 기본값: {"{warehouse}-{floor}{zone}-{row:02}-{level}"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 위치명 패턴 */}
|
||||
<div>
|
||||
<Label className="text-xs">위치명 패턴</Label>
|
||||
<Input
|
||||
value={config.namePattern || ""}
|
||||
onChange={(e) => handleChange("namePattern", e.target.value || undefined)}
|
||||
placeholder="{zone}-{row:02}열-{level}단"
|
||||
className="h-8 font-mono text-xs"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
||||
비워두면 기본값: {"{zone}-{row:02}열-{level}단"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 실시간 미리보기 */}
|
||||
<PatternPreview
|
||||
codePattern={config.codePattern}
|
||||
namePattern={config.namePattern}
|
||||
/>
|
||||
|
||||
{/* 사용 가능한 변수 목록 */}
|
||||
<div className="rounded-md border bg-muted/50 p-2">
|
||||
<div className="mb-1 text-[10px] font-medium text-foreground">사용 가능한 변수</div>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5">
|
||||
{PATTERN_VARIABLES.map((v) => (
|
||||
<div key={v.token} className="flex items-center gap-1 text-[10px]">
|
||||
<code className="rounded bg-primary/10 px-1 font-mono text-primary">{v.token}</code>
|
||||
<span className="text-muted-foreground">{v.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 제한 설정 */}
|
||||
<div className="space-y-3 border-t pt-3">
|
||||
<div className="text-sm font-medium text-foreground">제한 설정</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
// rack-structure는 v2-rack-structure의 patternUtils를 재사용
|
||||
export {
|
||||
applyLocationPattern,
|
||||
DEFAULT_CODE_PATTERN,
|
||||
DEFAULT_NAME_PATTERN,
|
||||
PATTERN_VARIABLES,
|
||||
} from "../v2-rack-structure/patternUtils";
|
||||
|
|
@ -20,7 +20,6 @@ import {
|
|||
GeneratedLocation,
|
||||
RackStructureContext,
|
||||
} from "./types";
|
||||
import { applyLocationPattern, DEFAULT_CODE_PATTERN, DEFAULT_NAME_PATTERN } from "./patternUtils";
|
||||
|
||||
// 기존 위치 데이터 타입
|
||||
interface ExistingLocation {
|
||||
|
|
@ -494,27 +493,23 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
|||
return { totalLocations, totalRows, maxLevel };
|
||||
}, [conditions]);
|
||||
|
||||
// 위치 코드 생성 (패턴 기반)
|
||||
// 위치 코드 생성
|
||||
const generateLocationCode = useCallback(
|
||||
(row: number, level: number): { code: string; name: string } => {
|
||||
const vars = {
|
||||
warehouse: context?.warehouseCode || "WH001",
|
||||
warehouseName: context?.warehouseName || "",
|
||||
floor: context?.floor || "1",
|
||||
zone: context?.zone || "A",
|
||||
row,
|
||||
level,
|
||||
};
|
||||
const warehouseCode = context?.warehouseCode || "WH001";
|
||||
const floor = context?.floor || "1";
|
||||
const zone = context?.zone || "A";
|
||||
|
||||
const codePattern = config.codePattern || DEFAULT_CODE_PATTERN;
|
||||
const namePattern = config.namePattern || DEFAULT_NAME_PATTERN;
|
||||
// 코드 생성 (예: WH001-1층D구역-01-1)
|
||||
const code = `${warehouseCode}-${floor}${zone}-${row.toString().padStart(2, "0")}-${level}`;
|
||||
|
||||
return {
|
||||
code: applyLocationPattern(codePattern, vars),
|
||||
name: applyLocationPattern(namePattern, vars),
|
||||
};
|
||||
// 이름 생성 - zone에 이미 "구역"이 포함되어 있으면 그대로 사용
|
||||
const zoneName = zone.includes("구역") ? zone : `${zone}구역`;
|
||||
const name = `${zoneName}-${row.toString().padStart(2, "0")}열-${level}단`;
|
||||
|
||||
return { code, name };
|
||||
},
|
||||
[context, config.codePattern, config.namePattern],
|
||||
[context],
|
||||
);
|
||||
|
||||
// 미리보기 생성
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
|
@ -12,47 +12,6 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { RackStructureComponentConfig, FieldMapping } from "./types";
|
||||
import { applyLocationPattern, DEFAULT_CODE_PATTERN, DEFAULT_NAME_PATTERN, PATTERN_VARIABLES } from "./patternUtils";
|
||||
|
||||
// 패턴 미리보기 서브 컴포넌트
|
||||
const PatternPreview: React.FC<{
|
||||
codePattern?: string;
|
||||
namePattern?: string;
|
||||
}> = ({ codePattern, namePattern }) => {
|
||||
const sampleVars = {
|
||||
warehouse: "WH002",
|
||||
warehouseName: "2창고",
|
||||
floor: "2층",
|
||||
zone: "A구역",
|
||||
row: 1,
|
||||
level: 3,
|
||||
};
|
||||
|
||||
const previewCode = useMemo(
|
||||
() => applyLocationPattern(codePattern || DEFAULT_CODE_PATTERN, sampleVars),
|
||||
[codePattern],
|
||||
);
|
||||
const previewName = useMemo(
|
||||
() => applyLocationPattern(namePattern || DEFAULT_NAME_PATTERN, sampleVars),
|
||||
[namePattern],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-primary/20 bg-primary/5 p-2.5">
|
||||
<div className="mb-1.5 text-[10px] font-medium text-primary">미리보기 (2창고 / 2층 / A구역 / 1열 / 3단)</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="w-14 shrink-0 text-muted-foreground">위치코드:</span>
|
||||
<code className="rounded bg-background px-1.5 py-0.5 font-mono text-foreground">{previewCode}</code>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="w-14 shrink-0 text-muted-foreground">위치명:</span>
|
||||
<code className="rounded bg-background px-1.5 py-0.5 font-mono text-foreground">{previewName}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RackStructureConfigPanelProps {
|
||||
config: RackStructureComponentConfig;
|
||||
|
|
@ -246,61 +205,6 @@ export const RackStructureConfigPanel: React.FC<RackStructureConfigPanelProps> =
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 위치코드 패턴 설정 */}
|
||||
<div className="space-y-3 border-t pt-3">
|
||||
<div className="text-sm font-medium text-foreground">위치코드/위치명 패턴</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
변수를 조합하여 위치코드와 위치명 생성 규칙을 설정하세요
|
||||
</p>
|
||||
|
||||
{/* 위치코드 패턴 */}
|
||||
<div>
|
||||
<Label className="text-xs">위치코드 패턴</Label>
|
||||
<Input
|
||||
value={config.codePattern || ""}
|
||||
onChange={(e) => handleChange("codePattern", e.target.value || undefined)}
|
||||
placeholder="{warehouse}-{floor}{zone}-{row:02}-{level}"
|
||||
className="h-8 font-mono text-xs"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
||||
비워두면 기본값: {"{warehouse}-{floor}{zone}-{row:02}-{level}"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 위치명 패턴 */}
|
||||
<div>
|
||||
<Label className="text-xs">위치명 패턴</Label>
|
||||
<Input
|
||||
value={config.namePattern || ""}
|
||||
onChange={(e) => handleChange("namePattern", e.target.value || undefined)}
|
||||
placeholder="{zone}-{row:02}열-{level}단"
|
||||
className="h-8 font-mono text-xs"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
||||
비워두면 기본값: {"{zone}-{row:02}열-{level}단"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 실시간 미리보기 */}
|
||||
<PatternPreview
|
||||
codePattern={config.codePattern}
|
||||
namePattern={config.namePattern}
|
||||
/>
|
||||
|
||||
{/* 사용 가능한 변수 목록 */}
|
||||
<div className="rounded-md border bg-muted/50 p-2">
|
||||
<div className="mb-1 text-[10px] font-medium text-foreground">사용 가능한 변수</div>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5">
|
||||
{PATTERN_VARIABLES.map((v) => (
|
||||
<div key={v.token} className="flex items-center gap-1 text-[10px]">
|
||||
<code className="rounded bg-primary/10 px-1 font-mono text-primary">{v.token}</code>
|
||||
<span className="text-muted-foreground">{v.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 제한 설정 */}
|
||||
<div className="space-y-3 border-t pt-3">
|
||||
<div className="text-sm font-medium text-foreground">제한 설정</div>
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* 위치코드/위치명 패턴 변환 유틸리티
|
||||
*
|
||||
* 사용 가능한 변수:
|
||||
* {warehouse} - 창고 코드 (예: WH002)
|
||||
* {warehouseName} - 창고명 (예: 2창고)
|
||||
* {floor} - 층 (예: 2층)
|
||||
* {zone} - 구역 (예: A구역)
|
||||
* {row} - 열 번호 (예: 1)
|
||||
* {row:02} - 열 번호 2자리 (예: 01)
|
||||
* {row:03} - 열 번호 3자리 (예: 001)
|
||||
* {level} - 단 번호 (예: 1)
|
||||
* {level:02} - 단 번호 2자리 (예: 01)
|
||||
* {level:03} - 단 번호 3자리 (예: 001)
|
||||
*/
|
||||
|
||||
interface PatternVariables {
|
||||
warehouse?: string;
|
||||
warehouseName?: string;
|
||||
floor?: string;
|
||||
zone?: string;
|
||||
row: number;
|
||||
level: number;
|
||||
}
|
||||
|
||||
// 기본 패턴 (하드코딩 대체)
|
||||
export const DEFAULT_CODE_PATTERN = "{warehouse}-{floor}{zone}-{row:02}-{level}";
|
||||
export const DEFAULT_NAME_PATTERN = "{zone}-{row:02}열-{level}단";
|
||||
|
||||
/**
|
||||
* 패턴 문자열에서 변수를 치환하여 결과 문자열 반환
|
||||
*/
|
||||
export function applyLocationPattern(pattern: string, vars: PatternVariables): string {
|
||||
let result = pattern;
|
||||
|
||||
// zone에 "구역" 포함 여부에 따른 처리 없이 있는 그대로 치환
|
||||
const simpleVars: Record<string, string | undefined> = {
|
||||
warehouse: vars.warehouse,
|
||||
warehouseName: vars.warehouseName,
|
||||
floor: vars.floor,
|
||||
zone: vars.zone,
|
||||
};
|
||||
|
||||
// 단순 문자열 변수 치환
|
||||
for (const [key, value] of Object.entries(simpleVars)) {
|
||||
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value || "");
|
||||
}
|
||||
|
||||
// 숫자 변수 (row, level) - zero-pad 지원
|
||||
const numericVars: Record<string, number> = {
|
||||
row: vars.row,
|
||||
level: vars.level,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(numericVars)) {
|
||||
// {row:02}, {level:03} 같은 zero-pad 패턴
|
||||
const padRegex = new RegExp(`\\{${key}:(\\d+)\\}`, "g");
|
||||
result = result.replace(padRegex, (_, padWidth) => {
|
||||
return value.toString().padStart(parseInt(padWidth), "0");
|
||||
});
|
||||
|
||||
// {row}, {level} 같은 단순 패턴
|
||||
result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value.toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 패턴에서 사용 가능한 변수 목록
|
||||
export const PATTERN_VARIABLES = [
|
||||
{ token: "{warehouse}", description: "창고 코드", example: "WH002" },
|
||||
{ token: "{warehouseName}", description: "창고명", example: "2창고" },
|
||||
{ token: "{floor}", description: "층", example: "2층" },
|
||||
{ token: "{zone}", description: "구역", example: "A구역" },
|
||||
{ token: "{row}", description: "열 번호", example: "1" },
|
||||
{ token: "{row:02}", description: "열 번호 (2자리)", example: "01" },
|
||||
{ token: "{row:03}", description: "열 번호 (3자리)", example: "001" },
|
||||
{ token: "{level}", description: "단 번호", example: "1" },
|
||||
{ token: "{level:02}", description: "단 번호 (2자리)", example: "01" },
|
||||
{ token: "{level:03}", description: "단 번호 (3자리)", example: "001" },
|
||||
];
|
||||
|
|
@ -271,8 +271,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
|
||||
const [resizingCompId, setResizingCompId] = useState<string | null>(null);
|
||||
const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null);
|
||||
// 🆕 외부에서 전달받은 선택 상태 사용 (탭 컴포넌트와 동일 구조)
|
||||
const selectedPanelComponentId = externalSelectedPanelComponentId || null;
|
||||
// 내부 선택 상태 (외부 prop 없을 때 fallback)
|
||||
const [internalSelectedCompId, setInternalSelectedCompId] = useState<string | null>(null);
|
||||
const selectedPanelComponentId = externalSelectedPanelComponentId ?? internalSelectedCompId;
|
||||
// 🆕 커스텀 모드: 분할패널 내 탭 컴포넌트의 선택 상태 관리
|
||||
const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState<string | undefined>(undefined);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
|
@ -3052,9 +3053,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="flex-1 overflow-auto p-4">
|
||||
<CardContent
|
||||
className="flex-1 overflow-auto p-4"
|
||||
{...(isDesignMode ? {
|
||||
"data-split-panel-container": "true",
|
||||
"data-component-id": component.id,
|
||||
"data-panel-side": "left",
|
||||
} : {})}
|
||||
>
|
||||
{/* 좌측 데이터 목록/테이블/커스텀 */}
|
||||
{console.log("🔍 [SplitPanel] 왼쪽 패널 displayMode:", componentConfig.leftPanel?.displayMode, "isDesignMode:", isDesignMode)}
|
||||
{componentConfig.leftPanel?.displayMode === "custom" ? (
|
||||
// 🆕 커스텀 모드: 패널 안에 자유롭게 컴포넌트 배치
|
||||
<div
|
||||
|
|
@ -3158,10 +3165,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 패널 컴포넌트 선택 시 탭 내 선택 해제
|
||||
if (comp.componentType !== "v2-tabs-widget") {
|
||||
setNestedTabSelectedCompId(undefined);
|
||||
}
|
||||
setInternalSelectedCompId(comp.id);
|
||||
onSelectPanelComponent?.("left", comp.id, comp);
|
||||
}}
|
||||
>
|
||||
|
|
@ -3917,7 +3924,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<CardContent className="flex-1 overflow-auto p-4">
|
||||
<CardContent
|
||||
className="flex-1 overflow-auto p-4"
|
||||
{...(isDesignMode ? {
|
||||
"data-split-panel-container": "true",
|
||||
"data-component-id": component.id,
|
||||
"data-panel-side": "right",
|
||||
} : {})}
|
||||
>
|
||||
{/* 추가 탭 컨텐츠 */}
|
||||
{activeTabIndex > 0 ? (
|
||||
(() => {
|
||||
|
|
@ -4289,6 +4303,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
if (comp.componentType !== "v2-tabs-widget") {
|
||||
setNestedTabSelectedCompId(undefined);
|
||||
}
|
||||
setInternalSelectedCompId(comp.id);
|
||||
onSelectPanelComponent?.("right", comp.id, comp);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -417,12 +417,39 @@ const TabsDesignEditor: React.FC<{
|
|||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
}}
|
||||
|
||||
>
|
||||
<div className="h-full w-full pointer-events-none">
|
||||
<div className={cn(
|
||||
"h-full w-full",
|
||||
comp.componentType !== "v2-split-panel-layout" && comp.componentType !== "split-panel-layout" && "pointer-events-none"
|
||||
)}>
|
||||
<DynamicComponentRenderer
|
||||
component={componentData as any}
|
||||
isDesignMode={true}
|
||||
formData={{}}
|
||||
{...(comp.componentType === "v2-split-panel-layout" || comp.componentType === "split-panel-layout" ? {
|
||||
onUpdateComponent: (updated: any) => {
|
||||
if (!onUpdateComponent) return;
|
||||
const updatedTabs = tabs.map((t) => {
|
||||
if (t.id !== activeTabId) return t;
|
||||
return {
|
||||
...t,
|
||||
components: (t.components || []).map((c) =>
|
||||
c.id === comp.id ? { ...c, componentConfig: updated.componentConfig || updated.overrides || c.componentConfig } : c
|
||||
),
|
||||
};
|
||||
});
|
||||
const configKey = component.componentConfig ? "componentConfig" : "overrides";
|
||||
const existingConfig = component[configKey] || {};
|
||||
onUpdateComponent({
|
||||
...component,
|
||||
[configKey]: { ...existingConfig, tabs: updatedTabs },
|
||||
});
|
||||
},
|
||||
onSelectPanelComponent: (panelSide: string, compId: string, panelComp: any) => {
|
||||
onSelectTabComponent?.(activeTabId, comp.id, { ...comp, _selectedPanelSide: panelSide, _selectedPanelCompId: compId, _selectedPanelComp: panelComp } as any);
|
||||
},
|
||||
} : {})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue