feat: add nested panel selection support in dynamic components
- Introduced a new callback `onNestedPanelSelect` to handle selections of components within nested split panels. - Updated the `RealtimePreviewDynamic`, `DynamicComponentRenderer`, and `TabsDesignEditor` components to support the new nested selection functionality. - Enhanced the layout management logic in `ScreenDesigner` to accommodate updates for nested structures, improving the overall user experience when interacting with nested components. Made-with: Cursor
This commit is contained in:
parent
f65b57410c
commit
966191786a
|
|
@ -47,6 +47,7 @@ interface RealtimePreviewProps {
|
||||||
selectedTabComponentId?: string; // 🆕 선택된 탭 컴포넌트 ID
|
selectedTabComponentId?: string; // 🆕 선택된 탭 컴포넌트 ID
|
||||||
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void; // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void; // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
selectedPanelComponentId?: string; // 🆕 선택된 분할 패널 컴포넌트 ID
|
selectedPanelComponentId?: string; // 🆕 선택된 분할 패널 컴포넌트 ID
|
||||||
|
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
||||||
onResize?: (componentId: string, newSize: { width: number; height: number }) => void; // 🆕 리사이즈 콜백
|
onResize?: (componentId: string, newSize: { width: number; height: number }) => void; // 🆕 리사이즈 콜백
|
||||||
|
|
||||||
// 버튼 액션을 위한 props
|
// 버튼 액션을 위한 props
|
||||||
|
|
@ -150,6 +151,7 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
selectedTabComponentId, // 🆕 선택된 탭 컴포넌트 ID
|
selectedTabComponentId, // 🆕 선택된 탭 컴포넌트 ID
|
||||||
onSelectPanelComponent, // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
onSelectPanelComponent, // 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
selectedPanelComponentId, // 🆕 선택된 분할 패널 컴포넌트 ID
|
selectedPanelComponentId, // 🆕 선택된 분할 패널 컴포넌트 ID
|
||||||
|
onNestedPanelSelect,
|
||||||
onResize, // 🆕 리사이즈 콜백
|
onResize, // 🆕 리사이즈 콜백
|
||||||
}) => {
|
}) => {
|
||||||
// 🆕 화면 다국어 컨텍스트
|
// 🆕 화면 다국어 컨텍스트
|
||||||
|
|
@ -768,6 +770,7 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
selectedTabComponentId={selectedTabComponentId}
|
selectedTabComponentId={selectedTabComponentId}
|
||||||
onSelectPanelComponent={onSelectPanelComponent}
|
onSelectPanelComponent={onSelectPanelComponent}
|
||||||
selectedPanelComponentId={selectedPanelComponentId}
|
selectedPanelComponentId={selectedPanelComponentId}
|
||||||
|
onNestedPanelSelect={onNestedPanelSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6744,15 +6744,6 @@ export default function ScreenDesigner({
|
||||||
const { splitPanelId, panelSide } = selectedPanelComponentInfo;
|
const { splitPanelId, panelSide } = selectedPanelComponentInfo;
|
||||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||||
|
|
||||||
console.log("🔧 updatePanelComponentProperty 호출:", {
|
|
||||||
componentId,
|
|
||||||
path,
|
|
||||||
value,
|
|
||||||
splitPanelId,
|
|
||||||
panelSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🆕 안전한 깊은 경로 업데이트 헬퍼 함수
|
|
||||||
const setNestedValue = (obj: any, pathStr: string, val: any): any => {
|
const setNestedValue = (obj: any, pathStr: string, val: any): any => {
|
||||||
const result = JSON.parse(JSON.stringify(obj));
|
const result = JSON.parse(JSON.stringify(obj));
|
||||||
const parts = pathStr.split(".");
|
const parts = pathStr.split(".");
|
||||||
|
|
@ -6769,9 +6760,27 @@ export default function ScreenDesigner({
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 중첩 구조 포함 분할패널 찾기 헬퍼
|
||||||
|
const findSplitPanelInLayout = (components: any[]): { found: any; path: "top" | "nested"; parentTabId?: string; parentTabTabId?: string } | null => {
|
||||||
|
const direct = components.find((c) => c.id === splitPanelId);
|
||||||
|
if (direct) return { found: direct, path: "top" };
|
||||||
|
for (const comp of components) {
|
||||||
|
const ct = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
||||||
|
const cfg = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
||||||
|
if (ct === "tabs-widget" || ct === "v2-tabs-widget") {
|
||||||
|
for (const tab of (cfg.tabs || [])) {
|
||||||
|
const nested = (tab.components || []).find((c: any) => c.id === splitPanelId);
|
||||||
|
if (nested) return { found: nested, path: "nested", parentTabId: comp.id, parentTabTabId: tab.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
setLayout((prevLayout) => {
|
setLayout((prevLayout) => {
|
||||||
const splitPanelComponent = prevLayout.components.find((c) => c.id === splitPanelId);
|
const result = findSplitPanelInLayout(prevLayout.components);
|
||||||
if (!splitPanelComponent) return prevLayout;
|
if (!result) return prevLayout;
|
||||||
|
const splitPanelComponent = result.found;
|
||||||
|
|
||||||
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
||||||
const panelConfig = currentConfig[panelKey] || {};
|
const panelConfig = currentConfig[panelKey] || {};
|
||||||
|
|
@ -6807,17 +6816,37 @@ export default function ScreenDesigner({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// selectedPanelComponentInfo 업데이트
|
|
||||||
setSelectedPanelComponentInfo((prev) =>
|
setSelectedPanelComponentInfo((prev) =>
|
||||||
prev ? { ...prev, component: updatedComp } : null,
|
prev ? { ...prev, component: updatedComp } : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
// 중첩 구조 반영
|
||||||
...prevLayout,
|
const applyUpdatedSplitPanel = (layout: any, updated: any, info: any) => {
|
||||||
components: prevLayout.components.map((c) =>
|
if (info.path === "top") {
|
||||||
c.id === splitPanelId ? updatedComponent : c,
|
return { ...layout, components: layout.components.map((c: any) => c.id === splitPanelId ? updated : c) };
|
||||||
),
|
}
|
||||||
|
return {
|
||||||
|
...layout,
|
||||||
|
components: layout.components.map((c: any) => {
|
||||||
|
if (c.id !== info.parentTabId) return c;
|
||||||
|
const cfgKey = c.componentConfig?.tabs ? "componentConfig" : "overrides";
|
||||||
|
const cfg = c[cfgKey] || {};
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
[cfgKey]: {
|
||||||
|
...cfg,
|
||||||
|
tabs: (cfg.tabs || []).map((t: any) =>
|
||||||
|
t.id === info.parentTabTabId
|
||||||
|
? { ...t, components: (t.components || []).map((tc: any) => tc.id === splitPanelId ? updated : tc) }
|
||||||
|
: t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return applyUpdatedSplitPanel(prevLayout, updatedComponent, result);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -6827,8 +6856,23 @@ export default function ScreenDesigner({
|
||||||
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
|
||||||
|
|
||||||
setLayout((prevLayout) => {
|
setLayout((prevLayout) => {
|
||||||
const splitPanelComponent = prevLayout.components.find((c) => c.id === splitPanelId);
|
const findResult = (() => {
|
||||||
if (!splitPanelComponent) return prevLayout;
|
const direct = prevLayout.components.find((c: any) => c.id === splitPanelId);
|
||||||
|
if (direct) return { found: direct, path: "top" as const };
|
||||||
|
for (const comp of prevLayout.components) {
|
||||||
|
const ct = (comp as any)?.componentType || (comp as any)?.overrides?.type;
|
||||||
|
const cfg = (comp as any)?.componentConfig || (comp as any)?.overrides || {};
|
||||||
|
if (ct === "tabs-widget" || ct === "v2-tabs-widget") {
|
||||||
|
for (const tab of (cfg.tabs || [])) {
|
||||||
|
const nested = (tab.components || []).find((c: any) => c.id === splitPanelId);
|
||||||
|
if (nested) return { found: nested, path: "nested" as const, parentTabId: comp.id, parentTabTabId: tab.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})();
|
||||||
|
if (!findResult) return prevLayout;
|
||||||
|
const splitPanelComponent = findResult.found;
|
||||||
|
|
||||||
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
const currentConfig = (splitPanelComponent as any).componentConfig || {};
|
||||||
const panelConfig = currentConfig[panelKey] || {};
|
const panelConfig = currentConfig[panelKey] || {};
|
||||||
|
|
@ -6849,11 +6893,27 @@ export default function ScreenDesigner({
|
||||||
|
|
||||||
setSelectedPanelComponentInfo(null);
|
setSelectedPanelComponentInfo(null);
|
||||||
|
|
||||||
|
if (findResult.path === "top") {
|
||||||
|
return { ...prevLayout, components: prevLayout.components.map((c: any) => c.id === splitPanelId ? updatedComponent : c) };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...prevLayout,
|
...prevLayout,
|
||||||
components: prevLayout.components.map((c) =>
|
components: prevLayout.components.map((c: any) => {
|
||||||
c.id === splitPanelId ? updatedComponent : c,
|
if (c.id !== findResult.parentTabId) return c;
|
||||||
),
|
const cfgKey = c.componentConfig?.tabs ? "componentConfig" : "overrides";
|
||||||
|
const cfg = c[cfgKey] || {};
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
[cfgKey]: {
|
||||||
|
...cfg,
|
||||||
|
tabs: (cfg.tabs || []).map((t: any) =>
|
||||||
|
t.id === findResult.parentTabTabId
|
||||||
|
? { ...t, components: (t.components || []).map((tc: any) => tc.id === splitPanelId ? updatedComponent : tc) }
|
||||||
|
: t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -7457,6 +7517,7 @@ export default function ScreenDesigner({
|
||||||
onSelectPanelComponent={(panelSide, compId, comp) =>
|
onSelectPanelComponent={(panelSide, compId, comp) =>
|
||||||
handleSelectPanelComponent(component.id, panelSide, compId, comp)
|
handleSelectPanelComponent(component.id, panelSide, compId, comp)
|
||||||
}
|
}
|
||||||
|
onNestedPanelSelect={handleSelectPanelComponent}
|
||||||
selectedPanelComponentId={
|
selectedPanelComponentId={
|
||||||
selectedPanelComponentInfo?.splitPanelId === component.id
|
selectedPanelComponentInfo?.splitPanelId === component.id
|
||||||
? selectedPanelComponentInfo.componentId
|
? selectedPanelComponentInfo.componentId
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,8 @@ export interface DynamicComponentRendererProps {
|
||||||
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void;
|
onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: any) => void;
|
||||||
selectedPanelComponentId?: string;
|
selectedPanelComponentId?: string;
|
||||||
|
// 중첩된 분할패널 내부 컴포넌트 선택 콜백 (탭 안의 분할패널)
|
||||||
|
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
||||||
flowSelectedStepId?: number | null;
|
flowSelectedStepId?: number | null;
|
||||||
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
|
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
|
||||||
// 테이블 새로고침 키
|
// 테이블 새로고침 키
|
||||||
|
|
@ -868,6 +870,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
// 🆕 분할 패널 내부 컴포넌트 선택 콜백
|
||||||
onSelectPanelComponent: props.onSelectPanelComponent,
|
onSelectPanelComponent: props.onSelectPanelComponent,
|
||||||
selectedPanelComponentId: props.selectedPanelComponentId,
|
selectedPanelComponentId: props.selectedPanelComponentId,
|
||||||
|
onNestedPanelSelect: props.onNestedPanelSelect,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 렌더러가 클래스인지 함수인지 확인
|
// 렌더러가 클래스인지 함수인지 확인
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ const TabsDesignEditor: React.FC<{
|
||||||
onUpdateComponent?: (updatedComponent: any) => void;
|
onUpdateComponent?: (updatedComponent: any) => void;
|
||||||
onSelectTabComponent?: (tabId: string, compId: string, comp: TabInlineComponent) => void;
|
onSelectTabComponent?: (tabId: string, compId: string, comp: TabInlineComponent) => void;
|
||||||
selectedTabComponentId?: string;
|
selectedTabComponentId?: string;
|
||||||
}> = ({ component, tabs, onUpdateComponent, onSelectTabComponent, selectedTabComponentId }) => {
|
onNestedPanelSelect?: (splitPanelId: string, panelSide: "left" | "right", compId: string, comp: any) => void;
|
||||||
|
}> = ({ component, tabs, onUpdateComponent, onSelectTabComponent, selectedTabComponentId, onNestedPanelSelect }) => {
|
||||||
const [activeTabId, setActiveTabId] = useState<string>(tabs[0]?.id || "");
|
const [activeTabId, setActiveTabId] = useState<string>(tabs[0]?.id || "");
|
||||||
const [draggingCompId, setDraggingCompId] = useState<string | null>(null);
|
const [draggingCompId, setDraggingCompId] = useState<string | null>(null);
|
||||||
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
|
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
|
||||||
|
|
@ -443,7 +444,11 @@ const TabsDesignEditor: React.FC<{
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSelectPanelComponent: (panelSide: string, compId: string, panelComp: any) => {
|
onSelectPanelComponent: (panelSide: string, compId: string, panelComp: any) => {
|
||||||
onSelectTabComponent?.(activeTabId, comp.id, { ...comp, _selectedPanelSide: panelSide, _selectedPanelCompId: compId, _selectedPanelComp: panelComp } as any);
|
if (onNestedPanelSelect) {
|
||||||
|
onNestedPanelSelect(comp.id, panelSide as "left" | "right", compId, panelComp);
|
||||||
|
} else {
|
||||||
|
onSelectTabComponent?.(activeTabId, comp.id, comp);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
} : {})}
|
} : {})}
|
||||||
/>
|
/>
|
||||||
|
|
@ -506,6 +511,7 @@ const TabsWidgetWrapper: React.FC<any> = (props) => {
|
||||||
onUpdateComponent,
|
onUpdateComponent,
|
||||||
onSelectTabComponent,
|
onSelectTabComponent,
|
||||||
selectedTabComponentId,
|
selectedTabComponentId,
|
||||||
|
onNestedPanelSelect,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -522,6 +528,7 @@ const TabsWidgetWrapper: React.FC<any> = (props) => {
|
||||||
onUpdateComponent={onUpdateComponent}
|
onUpdateComponent={onUpdateComponent}
|
||||||
onSelectTabComponent={onSelectTabComponent}
|
onSelectTabComponent={onSelectTabComponent}
|
||||||
selectedTabComponentId={selectedTabComponentId}
|
selectedTabComponentId={selectedTabComponentId}
|
||||||
|
onNestedPanelSelect={onNestedPanelSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue