diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx
index 7f161860..f770b4f7 100644
--- a/frontend/components/report/designer/CanvasComponent.tsx
+++ b/frontend/components/report/designer/CanvasComponent.tsx
@@ -9,8 +9,15 @@ interface CanvasComponentProps {
}
export function CanvasComponent({ component }: CanvasComponentProps) {
- const { selectedComponentId, selectComponent, updateComponent, getQueryResult, snapValueToGrid } =
- useReportDesigner();
+ const {
+ selectedComponentId,
+ selectComponent,
+ updateComponent,
+ getQueryResult,
+ snapValueToGrid,
+ calculateAlignmentGuides,
+ clearAlignmentGuides,
+ } = useReportDesigner();
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
@@ -54,10 +61,16 @@ 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);
+
+ // 정렬 가이드라인 계산
+ calculateAlignmentGuides(component.id, snappedX, snappedY, component.width, component.height);
+
// Grid Snap 적용
updateComponent(component.id, {
- x: snapValueToGrid(newX),
- y: snapValueToGrid(newY),
+ x: snappedX,
+ y: snappedY,
});
} else if (isResizing) {
const deltaX = e.clientX - resizeStart.x;
@@ -75,6 +88,8 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
const handleMouseUp = () => {
setIsDragging(false);
setIsResizing(false);
+ // 가이드라인 초기화
+ clearAlignmentGuides();
};
document.addEventListener("mousemove", handleMouseMove);
@@ -94,8 +109,12 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
resizeStart.width,
resizeStart.height,
component.id,
+ component.width,
+ component.height,
updateComponent,
snapValueToGrid,
+ calculateAlignmentGuides,
+ clearAlignmentGuides,
]);
// 표시할 값 결정
diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx
index 37e370fc..ad9f588e 100644
--- a/frontend/components/report/designer/ReportDesignerCanvas.tsx
+++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx
@@ -20,6 +20,7 @@ export function ReportDesignerCanvas() {
showGrid,
gridSize,
snapValueToGrid,
+ alignmentGuides,
} = useReportDesigner();
const [{ isOver }, drop] = useDrop(() => ({
@@ -109,6 +110,32 @@ export function ReportDesignerCanvas() {
}}
onClick={handleCanvasClick}
>
+ {/* 정렬 가이드라인 렌더링 */}
+ {alignmentGuides.vertical.map((x, index) => (
+
+ ))}
+ {alignmentGuides.horizontal.map((y, index) => (
+
+ ))}
+
{/* 컴포넌트 렌더링 */}
{components.map((component) => (
diff --git a/frontend/contexts/ReportDesignerContext.tsx b/frontend/contexts/ReportDesignerContext.tsx
index e8687dd3..ee8d0bfc 100644
--- a/frontend/contexts/ReportDesignerContext.tsx
+++ b/frontend/contexts/ReportDesignerContext.tsx
@@ -316,6 +316,11 @@ interface ReportDesignerContextType {
snapToGrid: boolean;
setSnapToGrid: (snap: boolean) => void;
snapValueToGrid: (value: number) => number;
+
+ // 정렬 가이드라인
+ alignmentGuides: { vertical: number[]; horizontal: number[] };
+ calculateAlignmentGuides: (draggingId: string, x: number, y: number, width: number, height: number) => void;
+ clearAlignmentGuides: () => void;
}
const ReportDesignerContext = createContext(undefined);
@@ -336,6 +341,12 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
const [showGrid, setShowGrid] = useState(true); // Grid 표시 여부
const [snapToGrid, setSnapToGrid] = useState(true); // Grid Snap 활성화
+ // 정렬 가이드라인
+ const [alignmentGuides, setAlignmentGuides] = useState<{
+ vertical: number[];
+ horizontal: number[];
+ }>({ vertical: [], horizontal: [] });
+
// Grid Snap 함수
const snapValueToGrid = useCallback(
(value: number): number => {
@@ -345,6 +356,61 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
[snapToGrid, gridSize],
);
+ // 정렬 가이드라인 계산 (드래그 중인 컴포넌트 제외)
+ const calculateAlignmentGuides = useCallback(
+ (draggingId: string, x: number, y: number, width: number, height: number) => {
+ const threshold = 5; // 정렬 감지 임계값 (px)
+ const verticalLines: number[] = [];
+ const horizontalLines: number[] = [];
+
+ // 드래그 중인 컴포넌트의 주요 위치
+ const left = x;
+ const right = x + width;
+ const centerX = x + width / 2;
+ const top = y;
+ const bottom = y + height;
+ const centerY = y + height / 2;
+
+ // 다른 컴포넌트들과 비교
+ components.forEach((comp) => {
+ if (comp.id === draggingId) return;
+
+ const compLeft = comp.x;
+ const compRight = comp.x + comp.width;
+ const compCenterX = comp.x + comp.width / 2;
+ const compTop = comp.y;
+ const compBottom = comp.y + comp.height;
+ const compCenterY = comp.y + comp.height / 2;
+
+ // 세로 정렬 체크 (left, center, right)
+ if (Math.abs(left - compLeft) < threshold) verticalLines.push(compLeft);
+ if (Math.abs(left - compRight) < threshold) verticalLines.push(compRight);
+ if (Math.abs(right - compLeft) < threshold) verticalLines.push(compLeft);
+ if (Math.abs(right - compRight) < threshold) verticalLines.push(compRight);
+ if (Math.abs(centerX - compCenterX) < threshold) verticalLines.push(compCenterX);
+
+ // 가로 정렬 체크 (top, center, bottom)
+ if (Math.abs(top - compTop) < threshold) horizontalLines.push(compTop);
+ if (Math.abs(top - compBottom) < threshold) horizontalLines.push(compBottom);
+ if (Math.abs(bottom - compTop) < threshold) horizontalLines.push(compTop);
+ if (Math.abs(bottom - compBottom) < threshold) horizontalLines.push(compBottom);
+ if (Math.abs(centerY - compCenterY) < threshold) horizontalLines.push(compCenterY);
+ });
+
+ // 중복 제거
+ setAlignmentGuides({
+ vertical: Array.from(new Set(verticalLines)),
+ horizontal: Array.from(new Set(horizontalLines)),
+ });
+ },
+ [components],
+ );
+
+ // 정렬 가이드라인 초기화
+ const clearAlignmentGuides = useCallback(() => {
+ setAlignmentGuides({ vertical: [], horizontal: [] });
+ }, []);
+
// 캔버스 설정 (기본값)
const [canvasWidth, setCanvasWidth] = useState(210);
const [canvasHeight, setCanvasHeight] = useState(297);
@@ -676,6 +742,10 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
snapToGrid,
setSnapToGrid,
snapValueToGrid,
+ // 정렬 가이드라인
+ alignmentGuides,
+ calculateAlignmentGuides,
+ clearAlignmentGuides,
};
return {children};