From 014979bebf92eb7453560302c4eb3879a5799977 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 12 Mar 2026 09:49:17 +0900 Subject: [PATCH] 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 --- frontend/components/screen/ScreenDesigner.tsx | 483 +++++++++++++++--- .../rack-structure/RackStructureComponent.tsx | 29 +- .../RackStructureConfigPanel.tsx | 98 +--- .../components/rack-structure/patternUtils.ts | 7 - .../RackStructureComponent.tsx | 29 +- .../RackStructureConfigPanel.tsx | 98 +--- .../v2-rack-structure/patternUtils.ts | 81 --- .../SplitPanelLayoutComponent.tsx | 27 +- .../v2-tabs-widget/tabs-component.tsx | 29 +- 9 files changed, 481 insertions(+), 400 deletions(-) delete mode 100644 frontend/lib/registry/components/rack-structure/patternUtils.ts delete mode 100644 frontend/lib/registry/components/v2-rack-structure/patternUtils.ts diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 10e1153d..e19f01f1 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -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, diff --git a/frontend/lib/registry/components/rack-structure/RackStructureComponent.tsx b/frontend/lib/registry/components/rack-structure/RackStructureComponent.tsx index 4c3d3112..63a4288a 100644 --- a/frontend/lib/registry/components/rack-structure/RackStructureComponent.tsx +++ b/frontend/lib/registry/components/rack-structure/RackStructureComponent.tsx @@ -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 = ({ 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], ); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒ์„ฑ diff --git a/frontend/lib/registry/components/rack-structure/RackStructureConfigPanel.tsx b/frontend/lib/registry/components/rack-structure/RackStructureConfigPanel.tsx index ddaebfa2..17e1a781 100644 --- a/frontend/lib/registry/components/rack-structure/RackStructureConfigPanel.tsx +++ b/frontend/lib/registry/components/rack-structure/RackStructureConfigPanel.tsx @@ -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 ( -
-
๋ฏธ๋ฆฌ๋ณด๊ธฐ (2์ฐฝ๊ณ  / 2์ธต / A๊ตฌ์—ญ / 1์—ด / 3๋‹จ)
-
-
- ์œ„์น˜์ฝ”๋“œ: - {previewCode} -
-
- ์œ„์น˜๋ช…: - {previewName} -
-
-
- ); -}; interface RackStructureConfigPanelProps { config: RackStructureComponentConfig; @@ -246,61 +205,6 @@ export const RackStructureConfigPanel: React.FC = - {/* ์œ„์น˜์ฝ”๋“œ ํŒจํ„ด ์„ค์ • */} -
-
์œ„์น˜์ฝ”๋“œ/์œ„์น˜๋ช… ํŒจํ„ด
-

- ๋ณ€์ˆ˜๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์œ„์น˜์ฝ”๋“œ์™€ ์œ„์น˜๋ช… ์ƒ์„ฑ ๊ทœ์น™์„ ์„ค์ •ํ•˜์„ธ์š” -

- - {/* ์œ„์น˜์ฝ”๋“œ ํŒจํ„ด */} -
- - handleChange("codePattern", e.target.value || undefined)} - placeholder="{warehouse}-{floor}{zone}-{row:02}-{level}" - className="h-8 font-mono text-xs" - /> -

- ๋น„์›Œ๋‘๋ฉด ๊ธฐ๋ณธ๊ฐ’: {"{warehouse}-{floor}{zone}-{row:02}-{level}"} -

-
- - {/* ์œ„์น˜๋ช… ํŒจํ„ด */} -
- - handleChange("namePattern", e.target.value || undefined)} - placeholder="{zone}-{row:02}์—ด-{level}๋‹จ" - className="h-8 font-mono text-xs" - /> -

- ๋น„์›Œ๋‘๋ฉด ๊ธฐ๋ณธ๊ฐ’: {"{zone}-{row:02}์—ด-{level}๋‹จ"} -

-
- - {/* ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} - - - {/* ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜ ๋ชฉ๋ก */} -
-
์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜
-
- {PATTERN_VARIABLES.map((v) => ( -
- {v.token} - {v.description} -
- ))} -
-
-
- {/* ์ œํ•œ ์„ค์ • */}
์ œํ•œ ์„ค์ •
diff --git a/frontend/lib/registry/components/rack-structure/patternUtils.ts b/frontend/lib/registry/components/rack-structure/patternUtils.ts deleted file mode 100644 index b5139c0b..00000000 --- a/frontend/lib/registry/components/rack-structure/patternUtils.ts +++ /dev/null @@ -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"; diff --git a/frontend/lib/registry/components/v2-rack-structure/RackStructureComponent.tsx b/frontend/lib/registry/components/v2-rack-structure/RackStructureComponent.tsx index cd107958..cef90668 100644 --- a/frontend/lib/registry/components/v2-rack-structure/RackStructureComponent.tsx +++ b/frontend/lib/registry/components/v2-rack-structure/RackStructureComponent.tsx @@ -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 = ({ 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], ); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒ์„ฑ diff --git a/frontend/lib/registry/components/v2-rack-structure/RackStructureConfigPanel.tsx b/frontend/lib/registry/components/v2-rack-structure/RackStructureConfigPanel.tsx index ddaebfa2..17e1a781 100644 --- a/frontend/lib/registry/components/v2-rack-structure/RackStructureConfigPanel.tsx +++ b/frontend/lib/registry/components/v2-rack-structure/RackStructureConfigPanel.tsx @@ -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 ( -
-
๋ฏธ๋ฆฌ๋ณด๊ธฐ (2์ฐฝ๊ณ  / 2์ธต / A๊ตฌ์—ญ / 1์—ด / 3๋‹จ)
-
-
- ์œ„์น˜์ฝ”๋“œ: - {previewCode} -
-
- ์œ„์น˜๋ช…: - {previewName} -
-
-
- ); -}; interface RackStructureConfigPanelProps { config: RackStructureComponentConfig; @@ -246,61 +205,6 @@ export const RackStructureConfigPanel: React.FC =
- {/* ์œ„์น˜์ฝ”๋“œ ํŒจํ„ด ์„ค์ • */} -
-
์œ„์น˜์ฝ”๋“œ/์œ„์น˜๋ช… ํŒจํ„ด
-

- ๋ณ€์ˆ˜๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์œ„์น˜์ฝ”๋“œ์™€ ์œ„์น˜๋ช… ์ƒ์„ฑ ๊ทœ์น™์„ ์„ค์ •ํ•˜์„ธ์š” -

- - {/* ์œ„์น˜์ฝ”๋“œ ํŒจํ„ด */} -
- - handleChange("codePattern", e.target.value || undefined)} - placeholder="{warehouse}-{floor}{zone}-{row:02}-{level}" - className="h-8 font-mono text-xs" - /> -

- ๋น„์›Œ๋‘๋ฉด ๊ธฐ๋ณธ๊ฐ’: {"{warehouse}-{floor}{zone}-{row:02}-{level}"} -

-
- - {/* ์œ„์น˜๋ช… ํŒจํ„ด */} -
- - handleChange("namePattern", e.target.value || undefined)} - placeholder="{zone}-{row:02}์—ด-{level}๋‹จ" - className="h-8 font-mono text-xs" - /> -

- ๋น„์›Œ๋‘๋ฉด ๊ธฐ๋ณธ๊ฐ’: {"{zone}-{row:02}์—ด-{level}๋‹จ"} -

-
- - {/* ์‹ค์‹œ๊ฐ„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} - - - {/* ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜ ๋ชฉ๋ก */} -
-
์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜
-
- {PATTERN_VARIABLES.map((v) => ( -
- {v.token} - {v.description} -
- ))} -
-
-
- {/* ์ œํ•œ ์„ค์ • */}
์ œํ•œ ์„ค์ •
diff --git a/frontend/lib/registry/components/v2-rack-structure/patternUtils.ts b/frontend/lib/registry/components/v2-rack-structure/patternUtils.ts deleted file mode 100644 index d226db82..00000000 --- a/frontend/lib/registry/components/v2-rack-structure/patternUtils.ts +++ /dev/null @@ -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 = { - 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 = { - 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" }, -]; diff --git a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx index 9d987d5e..1bc797d8 100644 --- a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx @@ -271,8 +271,9 @@ export const SplitPanelLayoutComponent: React.FC const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null); const [resizingCompId, setResizingCompId] = useState(null); const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null); - // ๐Ÿ†• ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ ํƒ ์ƒํƒœ ์‚ฌ์šฉ (ํƒญ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผ ๊ตฌ์กฐ) - const selectedPanelComponentId = externalSelectedPanelComponentId || null; + // ๋‚ด๋ถ€ ์„ ํƒ ์ƒํƒœ (์™ธ๋ถ€ prop ์—†์„ ๋•Œ fallback) + const [internalSelectedCompId, setInternalSelectedCompId] = useState(null); + const selectedPanelComponentId = externalSelectedPanelComponentId ?? internalSelectedCompId; // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋ถ„ํ• ํŒจ๋„ ๋‚ด ํƒญ ์ปดํฌ๋„ŒํŠธ์˜ ์„ ํƒ ์ƒํƒœ ๊ด€๋ฆฌ const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState(undefined); const rafRef = useRef(null); @@ -3052,9 +3053,15 @@ export const SplitPanelLayoutComponent: React.FC
)} - + {/* ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋ชฉ๋ก/ํ…Œ์ด๋ธ”/์ปค์Šคํ…€ */} - {console.log("๐Ÿ” [SplitPanel] ์™ผ์ชฝ ํŒจ๋„ displayMode:", componentConfig.leftPanel?.displayMode, "isDesignMode:", isDesignMode)} {componentConfig.leftPanel?.displayMode === "custom" ? ( // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ํŒจ๋„ ์•ˆ์— ์ž์œ ๋กญ๊ฒŒ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์น˜
}} 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
)} - + {/* ์ถ”๊ฐ€ ํƒญ ์ปจํ…์ธ  */} {activeTabIndex > 0 ? ( (() => { @@ -4289,6 +4303,7 @@ export const SplitPanelLayoutComponent: React.FC if (comp.componentType !== "v2-tabs-widget") { setNestedTabSelectedCompId(undefined); } + setInternalSelectedCompId(comp.id); onSelectPanelComponent?.("right", comp.id, comp); }} > diff --git a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx index 491b45e4..ad42938c 100644 --- a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx +++ b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx @@ -417,12 +417,39 @@ const TabsDesignEditor: React.FC<{ width: displayWidth, height: displayHeight, }} + > -
+
{ + 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); + }, + } : {})} />