From b4bfb9964fed2d0a02574624ee558c6c7b3834a8 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 16 Jan 2026 17:20:11 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=88=98=EC=A7=81=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80:?= =?UTF-8?q?=20ScreenViewPage=EC=97=90=EC=84=9C=20=EA=B0=99=EC=9D=80=20X=20?= =?UTF-8?q?=EC=98=81=EC=97=AD=20=EB=82=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=93=A4=EC=9D=B4=20=EA=B2=B9=EC=B9=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=9E=90=EB=8F=99=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20Y=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=98=EC=98=80=EC=8A=B5=EB=8B=88=EB=8B=A4?= =?UTF-8?q?.=20=EB=98=90=ED=95=9C,=20RepeatContainerComponent=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B3=A0=EC=A0=95=20=EB=86=92=EC=9D=B4=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20=EC=9E=90=EB=8F=99=20=EB=86=92=EC=9D=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EC=97=AC=20=EB=82=B4=EB=B6=80=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=EA=B0=80=20=EC=BB=A4=EC=A7=88=20=EB=95=8C=20?= =?UTF-8?q?=EC=9C=A0=EC=97=B0=ED=95=98=EA=B2=8C=20=EB=8C=80=EC=9D=91?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EC=98=80=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(main)/screens/[screenId]/page.tsx | 86 +++++++++++++++---- .../RepeatContainerComponent.tsx | 26 +++--- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 133a7256..b4f5b3df 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -489,8 +489,73 @@ function ScreenViewPage() { (c as any).componentType === "conditional-container", ); - // TableSearchWidget 및 조건부 컨테이너 높이 차이를 계산하여 Y 위치 조정 - const adjustedComponents = regularComponents.map((component) => { + // 🆕 같은 X 영역(섹션)에서 컴포넌트들이 겹치지 않도록 자동 수직 정렬 + const autoLayoutComponents = (() => { + // X 위치 기준으로 섹션 그룹화 (50px 오차 범위) + const X_THRESHOLD = 50; + const GAP = 16; // 컴포넌트 간 간격 + + // 컴포넌트를 X 섹션별로 그룹화 + const sections: Map = new Map(); + + regularComponents.forEach((comp) => { + const x = comp.position.x; + let foundSection = false; + + for (const [sectionX, components] of sections.entries()) { + if (Math.abs(x - sectionX) < X_THRESHOLD) { + components.push(comp); + foundSection = true; + break; + } + } + + if (!foundSection) { + sections.set(x, [comp]); + } + }); + + // 각 섹션 내에서 Y 위치 순으로 정렬 후 자동 배치 + const adjustedMap = new Map(); + + for (const [sectionX, components] of sections.entries()) { + // 섹션 내 2개 이상 컴포넌트가 있을 때만 자동 배치 + if (components.length >= 2) { + // Y 위치 순으로 정렬 + const sorted = [...components].sort((a, b) => a.position.y - b.position.y); + + let currentY = sorted[0].position.y; + + sorted.forEach((comp, index) => { + if (index === 0) { + adjustedMap.set(comp.id, comp); + } else { + // 이전 컴포넌트 아래로 배치 + const prevComp = sorted[index - 1]; + const prevAdjusted = adjustedMap.get(prevComp.id) || prevComp; + const prevBottom = prevAdjusted.position.y + (prevAdjusted.size?.height || 100); + const newY = prevBottom + GAP; + + adjustedMap.set(comp.id, { + ...comp, + position: { + ...comp.position, + y: newY, + }, + }); + } + }); + } else { + // 단일 컴포넌트는 그대로 + components.forEach((comp) => adjustedMap.set(comp.id, comp)); + } + } + + return regularComponents.map((comp) => adjustedMap.get(comp.id) || comp); + })(); + + // TableSearchWidget 및 조건부 컨테이너 높이 차이를 계산하여 Y 위치 추가 조정 + const adjustedComponents = autoLayoutComponents.map((component) => { const isTableSearchWidget = (component as any).componentId === "table-search-widget"; const isConditionalContainer = (component as any).componentId === "conditional-container"; @@ -511,30 +576,15 @@ function ScreenViewPage() { } } - // 🆕 조건부 컨테이너 높이 조정 + // 조건부 컨테이너 높이 조정 for (const container of conditionalContainers) { const isBelow = component.position.y > container.position.y; const actualHeight = conditionalContainerHeights[container.id]; const originalHeight = container.size?.height || 200; const heightDiff = actualHeight ? actualHeight - originalHeight : 0; - console.log(`🔍 높이 조정 체크:`, { - componentId: component.id, - componentY: component.position.y, - containerY: container.position.y, - isBelow, - actualHeight, - originalHeight, - heightDiff, - containerId: container.id, - containerSize: container.size, - }); - if (isBelow && heightDiff > 0) { totalHeightAdjustment += heightDiff; - console.log( - `📐 컴포넌트 ${component.id} 위치 조정: ${heightDiff}px (조건부 컨테이너 ${container.id})`, - ); } } diff --git a/frontend/lib/registry/components/repeat-container/RepeatContainerComponent.tsx b/frontend/lib/registry/components/repeat-container/RepeatContainerComponent.tsx index bb689c98..b78e6f0c 100644 --- a/frontend/lib/registry/components/repeat-container/RepeatContainerComponent.tsx +++ b/frontend/lib/registry/components/repeat-container/RepeatContainerComponent.tsx @@ -307,12 +307,15 @@ export function RepeatContainerComponent({ return { minWidth: itemMinWidth, maxWidth: itemMaxWidth, - height: itemHeight, + // height 대신 minHeight 사용 - 내부 컨텐츠가 커지면 자동으로 높이 확장 + minHeight: itemHeight || "auto", + height: "auto", // 고정 높이 대신 auto로 변경 backgroundColor: backgroundColor || "#ffffff", borderRadius: borderRadius || "8px", padding: padding || "16px", border: showBorder ? "1px solid #e5e7eb" : "none", boxShadow: showShadow ? "0 1px 3px rgba(0,0,0,0.1)" : "none", + overflow: "visible", // 내부 컨텐츠가 튀어나가지 않도록 }; }, [itemMinWidth, itemMaxWidth, itemHeight, backgroundColor, borderRadius, padding, showBorder, showShadow]); @@ -343,11 +346,11 @@ export function RepeatContainerComponent({ _isLast: context.isLast, }; - // 슬롯에 배치된 컴포넌트들을 렌더링 + // 슬롯에 배치된 컴포넌트들을 렌더링 (Flow 레이아웃으로 변경) return ( -
+
{slotChildren.map((childComp: SlotComponentConfig) => { - const { position = { x: 0, y: 0 }, size = { width: 200, height: 40 } } = childComp; + const { size = { width: "100%", height: "auto" } } = childComp; // DynamicComponentRenderer가 기대하는 형식으로 변환 const componentData = { @@ -355,8 +358,11 @@ export function RepeatContainerComponent({ componentType: childComp.componentType, label: childComp.label, columnName: childComp.fieldName, - position: { ...position, z: 1 }, - size, + position: { x: 0, y: 0, z: 1 }, + size: { + width: typeof size.width === "number" ? size.width : undefined, + height: typeof size.height === "number" ? size.height : undefined, + }, componentConfig: childComp.componentConfig, style: childComp.style, }; @@ -364,12 +370,10 @@ export function RepeatContainerComponent({ return (