diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx index 1b32fe33..504d1176 100644 --- a/frontend/components/report/designer/CanvasComponent.tsx +++ b/frontend/components/report/designer/CanvasComponent.tsx @@ -20,6 +20,8 @@ export function CanvasComponent({ component }: CanvasComponentProps) { snapValueToGrid, calculateAlignmentGuides, clearAlignmentGuides, + canvasWidth, + canvasHeight, } = useReportDesigner(); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); @@ -96,8 +98,20 @@ export function CanvasComponent({ component }: CanvasComponentProps) { if (isDragging) { const newX = Math.max(0, e.clientX - dragStart.x); const newY = Math.max(0, e.clientY - dragStart.y); - const snappedX = snapValueToGrid(newX); - const snappedY = snapValueToGrid(newY); + + // 캔버스 경계 체크 (mm를 px로 변환: 1mm ≈ 3.7795px) + const canvasWidthPx = canvasWidth * 3.7795; + const canvasHeightPx = canvasHeight * 3.7795; + + // 컴포넌트가 캔버스 안에 있도록 제한 + const maxX = canvasWidthPx - component.width; + const maxY = canvasHeightPx - component.height; + + const boundedX = Math.min(Math.max(0, newX), maxX); + const boundedY = Math.min(Math.max(0, newY), maxY); + + const snappedX = snapValueToGrid(boundedX); + const snappedY = snapValueToGrid(boundedY); // 정렬 가이드라인 계산 calculateAlignmentGuides(component.id, snappedX, snappedY, component.width, component.height); @@ -116,9 +130,16 @@ export function CanvasComponent({ component }: CanvasComponentProps) { if (isGrouped) { components.forEach((c) => { if (c.groupId === component.groupId && c.id !== component.id) { + const newGroupX = c.x + deltaX; + const newGroupY = c.y + deltaY; + + // 그룹 컴포넌트도 경계 체크 + const groupMaxX = canvasWidthPx - c.width; + const groupMaxY = canvasHeightPx - c.height; + updateComponent(c.id, { - x: c.x + deltaX, - y: c.y + deltaY, + x: Math.min(Math.max(0, newGroupX), groupMaxX), + y: Math.min(Math.max(0, newGroupY), groupMaxY), }); } }); @@ -128,10 +149,22 @@ export function CanvasComponent({ component }: CanvasComponentProps) { const deltaY = e.clientY - resizeStart.y; const newWidth = Math.max(50, resizeStart.width + deltaX); const newHeight = Math.max(30, resizeStart.height + deltaY); + + // 캔버스 경계 체크 + const canvasWidthPx = canvasWidth * 3.7795; + const canvasHeightPx = canvasHeight * 3.7795; + + // 컴포넌트가 캔버스를 벗어나지 않도록 최대 크기 제한 + const maxWidth = canvasWidthPx - component.x; + const maxHeight = canvasHeightPx - component.y; + + const boundedWidth = Math.min(newWidth, maxWidth); + const boundedHeight = Math.min(newHeight, maxHeight); + // Grid Snap 적용 updateComponent(component.id, { - width: snapValueToGrid(newWidth), - height: snapValueToGrid(newHeight), + width: snapValueToGrid(boundedWidth), + height: snapValueToGrid(boundedHeight), }); } }; @@ -171,6 +204,8 @@ export function CanvasComponent({ component }: CanvasComponentProps) { snapValueToGrid, calculateAlignmentGuides, clearAlignmentGuides, + canvasWidth, + canvasHeight, ]); // 표시할 값 결정 diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx index 9f37e965..6b534b08 100644 --- a/frontend/components/report/designer/ReportDesignerCanvas.tsx +++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx @@ -265,9 +265,9 @@ export function ReportDesignerCanvas() {
작업 영역
{/* 캔버스 스크롤 영역 */} -
+
{/* 눈금자와 캔버스를 감싸는 컨테이너 */} -
+
{/* 좌상단 코너 + 가로 눈금자 */} {showRuler && (