From ae23a4408e612154081de803dd43af50d04c3a8c Mon Sep 17 00:00:00 2001 From: dohyeons Date: Wed, 1 Oct 2025 15:32:35 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BA=94=EB=B2=84=EC=8A=A4=EC=97=90=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/designer/CanvasComponent.tsx | 16 +++++-- .../report/designer/ReportDesignerCanvas.tsx | 31 ++++++++++--- .../report/designer/ReportDesignerToolbar.tsx | 35 +++++++++++++-- frontend/contexts/ReportDesignerContext.tsx | 45 +++++++++++++++++-- 4 files changed, 111 insertions(+), 16 deletions(-) diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx index 5d9ac69a..7f161860 100644 --- a/frontend/components/report/designer/CanvasComponent.tsx +++ b/frontend/components/report/designer/CanvasComponent.tsx @@ -9,7 +9,8 @@ interface CanvasComponentProps { } export function CanvasComponent({ component }: CanvasComponentProps) { - const { selectedComponentId, selectComponent, updateComponent, getQueryResult } = useReportDesigner(); + const { selectedComponentId, selectComponent, updateComponent, getQueryResult, snapValueToGrid } = + useReportDesigner(); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); @@ -53,13 +54,21 @@ 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); - updateComponent(component.id, { x: newX, y: newY }); + // Grid Snap 적용 + updateComponent(component.id, { + x: snapValueToGrid(newX), + y: snapValueToGrid(newY), + }); } else if (isResizing) { const deltaX = e.clientX - resizeStart.x; const deltaY = e.clientY - resizeStart.y; const newWidth = Math.max(50, resizeStart.width + deltaX); const newHeight = Math.max(30, resizeStart.height + deltaY); - updateComponent(component.id, { width: newWidth, height: newHeight }); + // Grid Snap 적용 + updateComponent(component.id, { + width: snapValueToGrid(newWidth), + height: snapValueToGrid(newHeight), + }); } }; @@ -86,6 +95,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) { resizeStart.height, component.id, updateComponent, + snapValueToGrid, ]); // 표시할 값 결정 diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx index 74f56892..37e370fc 100644 --- a/frontend/components/report/designer/ReportDesignerCanvas.tsx +++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx @@ -9,8 +9,18 @@ import { v4 as uuidv4 } from "uuid"; export function ReportDesignerCanvas() { const canvasRef = useRef(null); - const { components, addComponent, canvasWidth, canvasHeight, selectComponent, selectedComponentId, removeComponent } = - useReportDesigner(); + const { + components, + addComponent, + canvasWidth, + canvasHeight, + selectComponent, + selectedComponentId, + removeComponent, + showGrid, + gridSize, + snapValueToGrid, + } = useReportDesigner(); const [{ isOver }, drop] = useDrop(() => ({ accept: "component", @@ -25,14 +35,14 @@ export function ReportDesignerCanvas() { const x = offset.x - canvasRect.left; const y = offset.y - canvasRect.top; - // 새 컴포넌트 생성 + // 새 컴포넌트 생성 (Grid Snap 적용) const newComponent: ComponentConfig = { id: `comp_${uuidv4()}`, type: item.componentType, - x: Math.max(0, x - 100), - y: Math.max(0, y - 25), - width: 200, - height: item.componentType === "table" ? 200 : 100, + x: snapValueToGrid(Math.max(0, x - 100)), + y: snapValueToGrid(Math.max(0, y - 25)), + width: snapValueToGrid(200), + height: snapValueToGrid(item.componentType === "table" ? 200 : 100), zIndex: components.length, fontSize: 13, fontFamily: "Malgun Gothic", @@ -89,6 +99,13 @@ export function ReportDesignerCanvas() { style={{ width: `${canvasWidth}mm`, minHeight: `${canvasHeight}mm`, + backgroundImage: showGrid + ? ` + linear-gradient(to right, #e5e7eb 1px, transparent 1px), + linear-gradient(to bottom, #e5e7eb 1px, transparent 1px) + ` + : undefined, + backgroundSize: showGrid ? `${gridSize}px ${gridSize}px` : undefined, }} onClick={handleCanvasClick} > diff --git a/frontend/components/report/designer/ReportDesignerToolbar.tsx b/frontend/components/report/designer/ReportDesignerToolbar.tsx index bb49e6bd..66b50a73 100644 --- a/frontend/components/report/designer/ReportDesignerToolbar.tsx +++ b/frontend/components/report/designer/ReportDesignerToolbar.tsx @@ -1,7 +1,7 @@ "use client"; import { Button } from "@/components/ui/button"; -import { Save, Eye, RotateCcw, ArrowLeft, Loader2, BookTemplate } from "lucide-react"; +import { Save, Eye, RotateCcw, ArrowLeft, Loader2, BookTemplate, Grid3x3 } from "lucide-react"; import { useRouter } from "next/navigation"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import { useState } from "react"; @@ -13,8 +13,20 @@ import { ReportPreviewModal } from "./ReportPreviewModal"; export function ReportDesignerToolbar() { const router = useRouter(); - const { reportDetail, saveLayout, isSaving, loadLayout, components, canvasWidth, canvasHeight, queries } = - useReportDesigner(); + const { + reportDetail, + saveLayout, + isSaving, + loadLayout, + components, + canvasWidth, + canvasHeight, + queries, + snapToGrid, + setSnapToGrid, + showGrid, + setShowGrid, + } = useReportDesigner(); const [showPreview, setShowPreview] = useState(false); const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false); const { toast } = useToast(); @@ -22,6 +34,13 @@ export function ReportDesignerToolbar() { // 템플릿 저장 가능 여부: 컴포넌트가 있어야 함 const canSaveAsTemplate = components.length > 0; + // Grid 토글 (Snap과 Grid 표시 함께 제어) + const handleToggleGrid = () => { + const newValue = !snapToGrid; + setSnapToGrid(newValue); + setShowGrid(newValue); + }; + const handleSave = async () => { await saveLayout(); }; @@ -120,6 +139,16 @@ export function ReportDesignerToolbar() {
+