From a1ddf4678d897a592fad570a650a4a827bd23f1b Mon Sep 17 00:00:00 2001 From: dohyeons Date: Wed, 1 Oct 2025 16:23:20 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=A0=EA=B8=88=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/designer/CanvasComponent.tsx | 37 ++++++++-- .../report/designer/ReportDesignerCanvas.tsx | 18 +++-- .../report/designer/ReportDesignerToolbar.tsx | 36 ++++++++++ frontend/contexts/ReportDesignerContext.tsx | 68 +++++++++++++++++++ frontend/types/report.ts | 1 + 5 files changed, 151 insertions(+), 9 deletions(-) diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx index b6a16839..cf58991c 100644 --- a/frontend/components/report/designer/CanvasComponent.tsx +++ b/frontend/components/report/designer/CanvasComponent.tsx @@ -27,6 +27,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) { const isSelected = selectedComponentId === component.id; const isMultiSelected = selectedComponentIds.includes(component.id); + const isLocked = component.locked === true; // 드래그 시작 const handleMouseDown = (e: React.MouseEvent) => { @@ -34,6 +35,15 @@ export function CanvasComponent({ component }: CanvasComponentProps) { return; } + // 잠긴 컴포넌트는 드래그 불가 + if (isLocked) { + e.stopPropagation(); + // Ctrl/Cmd 키 감지 (다중 선택) + const isMultiSelect = e.ctrlKey || e.metaKey; + selectComponent(component.id, isMultiSelect); + return; + } + e.stopPropagation(); // Ctrl/Cmd 키 감지 (다중 선택) @@ -49,6 +59,12 @@ export function CanvasComponent({ component }: CanvasComponentProps) { // 리사이즈 시작 const handleResizeStart = (e: React.MouseEvent) => { + // 잠긴 컴포넌트는 리사이즈 불가 + if (isLocked) { + e.stopPropagation(); + return; + } + e.stopPropagation(); setIsResizing(true); setResizeStart({ @@ -277,8 +293,16 @@ export function CanvasComponent({ component }: CanvasComponentProps) { return (
{renderContent()} - {/* 리사이즈 핸들 (선택된 경우만) */} - {isSelected && ( + {/* 잠금 표시 */} + {isLocked && ( +
🔒
+ )} + + {/* 리사이즈 핸들 (선택된 경우만, 잠금 안 된 경우만) */} + {isSelected && !isLocked && (
0 ? selectedComponentIds : ([selectedComponentId].filter(Boolean) as string[]); - // 각 컴포넌트 이동 + // 각 컴포넌트 이동 (잠긴 컴포넌트는 제외) idsToMove.forEach((id) => { const component = components.find((c) => c.id === id); - if (!component) return; + if (!component || component.locked) return; let newX = component.x; let newY = component.y; @@ -132,12 +132,20 @@ export function ReportDesignerCanvas() { return; } - // Delete 키: 삭제 + // Delete 키: 삭제 (잠긴 컴포넌트는 제외) if (e.key === "Delete") { if (selectedComponentIds.length > 0) { - selectedComponentIds.forEach((id) => removeComponent(id)); + selectedComponentIds.forEach((id) => { + const component = components.find((c) => c.id === id); + if (component && !component.locked) { + removeComponent(id); + } + }); } else if (selectedComponentId) { - removeComponent(selectedComponentId); + const component = components.find((c) => c.id === selectedComponentId); + if (component && !component.locked) { + removeComponent(selectedComponentId); + } } } diff --git a/frontend/components/report/designer/ReportDesignerToolbar.tsx b/frontend/components/report/designer/ReportDesignerToolbar.tsx index f1dcde6f..3b637011 100644 --- a/frontend/components/report/designer/ReportDesignerToolbar.tsx +++ b/frontend/components/report/designer/ReportDesignerToolbar.tsx @@ -26,6 +26,8 @@ import { ChevronsDown, ChevronsUp, ChevronUp, + Lock, + Unlock, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; @@ -78,6 +80,9 @@ export function ReportDesignerToolbar() { sendToBack, bringForward, sendBackward, + toggleLock, + lockComponents, + unlockComponents, } = useReportDesigner(); const [showPreview, setShowPreview] = useState(false); const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false); @@ -357,6 +362,37 @@ export function ReportDesignerToolbar() { + {/* 잠금 드롭다운 */} + + + + + + + + 토글 (잠금/해제) + + + + + 잠금 설정 + + + + 잠금 해제 + + + +