From de97c4051773f0f93b771452421a0a01e23c437e Mon Sep 17 00:00:00 2001 From: dohyeons Date: Wed, 1 Oct 2025 14:14:06 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=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 | 44 ++-- .../report/designer/ReportDesignerCanvas.tsx | 17 +- .../designer/ReportDesignerRightPanel.tsx | 206 ++++++++++++++---- .../report/designer/ReportPreviewModal.tsx | 12 +- 4 files changed, 218 insertions(+), 61 deletions(-) diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx index efb8107b..5f9b0aef 100644 --- a/frontend/components/report/designer/CanvasComponent.tsx +++ b/frontend/components/report/designer/CanvasComponent.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRef, useState } from "react"; +import { useRef, useState, useEffect } from "react"; import { ComponentConfig } from "@/types/report"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; @@ -46,7 +46,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) { }; // 마우스 이동 핸들러 (전역) - useState(() => { + useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (isDragging) { const newX = Math.max(0, e.clientX - dragStart.x); @@ -74,7 +74,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) { document.removeEventListener("mouseup", handleMouseUp); }; } - }); + }, [isDragging, isResizing, dragStart, resizeStart, component.id, updateComponent]); // 표시할 값 결정 const getDisplayValue = (): string => { @@ -119,14 +119,17 @@ export function CanvasComponent({ component }: CanvasComponentProps) { 텍스트 필드 {hasBinding && ● 연결됨} - e.stopPropagation()} - /> +
+ {displayValue} +
); @@ -137,7 +140,16 @@ export function CanvasComponent({ component }: CanvasComponentProps) { 레이블 {hasBinding && ● 연결됨} -
{displayValue}
+
+ {displayValue} +
); @@ -218,15 +230,17 @@ export function CanvasComponent({ component }: CanvasComponentProps) { return (
diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx index c56a298e..0cea297f 100644 --- a/frontend/components/report/designer/ReportDesignerCanvas.tsx +++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRef } from "react"; +import { useRef, useEffect } from "react"; import { useDrop } from "react-dnd"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import { ComponentConfig } from "@/types/report"; @@ -9,7 +9,8 @@ import { v4 as uuidv4 } from "uuid"; export function ReportDesignerCanvas() { const canvasRef = useRef(null); - const { components, addComponent, canvasWidth, canvasHeight, selectComponent } = useReportDesigner(); + const { components, addComponent, canvasWidth, canvasHeight, selectComponent, selectedComponentId, removeComponent } = + useReportDesigner(); const [{ isOver }, drop] = useDrop(() => ({ accept: "component", @@ -60,6 +61,18 @@ export function ReportDesignerCanvas() { } }; + // Delete 키 삭제 처리 + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Delete" && selectedComponentId) { + removeComponent(selectedComponentId); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedComponentId, removeComponent]); + return (
{/* 작업 영역 제목 */} diff --git a/frontend/components/report/designer/ReportDesignerRightPanel.tsx b/frontend/components/report/designer/ReportDesignerRightPanel.tsx index ae3e4e66..5989e2d5 100644 --- a/frontend/components/report/designer/ReportDesignerRightPanel.tsx +++ b/frontend/components/report/designer/ReportDesignerRightPanel.tsx @@ -131,49 +131,173 @@ export function ReportDesignerRightPanel() {
- {/* 글꼴 크기 */} -
- - - updateComponent(selectedComponent.id, { - fontSize: parseInt(e.target.value) || 13, - }) - } - className="h-8" - /> -
+ {/* 스타일링 섹션 */} +
+

스타일

- {/* 글꼴 색상 */} -
- - - updateComponent(selectedComponent.id, { - fontColor: e.target.value, - }) - } - className="h-8" - /> -
+ {/* 글꼴 크기 */} +
+ + + updateComponent(selectedComponent.id, { + fontSize: parseInt(e.target.value) || 13, + }) + } + className="h-8" + /> +
- {/* 배경 색상 */} -
- - - updateComponent(selectedComponent.id, { - backgroundColor: e.target.value, - }) - } - className="h-8" - /> + {/* 글꼴 색상 */} +
+ +
+ + updateComponent(selectedComponent.id, { + fontColor: e.target.value, + }) + } + className="h-8 w-16" + /> + + updateComponent(selectedComponent.id, { + fontColor: e.target.value, + }) + } + className="h-8 flex-1 font-mono text-xs" + /> +
+
+ + {/* 텍스트 정렬 (텍스트/라벨만) */} + {(selectedComponent.type === "text" || selectedComponent.type === "label") && ( +
+ + +
+ )} + + {/* 글꼴 굵기 (텍스트/라벨만) */} + {(selectedComponent.type === "text" || selectedComponent.type === "label") && ( +
+ + +
+ )} + + {/* 배경 색상 */} +
+ +
+ + updateComponent(selectedComponent.id, { + backgroundColor: e.target.value, + }) + } + className="h-8 w-16" + /> + + updateComponent(selectedComponent.id, { + backgroundColor: e.target.value, + }) + } + placeholder="transparent" + className="h-8 flex-1 font-mono text-xs" + /> + +
+
+ + {/* 테두리 */} +
+ +
+
+ + + updateComponent(selectedComponent.id, { + borderWidth: parseInt(e.target.value) || 0, + }) + } + className="h-8" + /> +
+
+ + + updateComponent(selectedComponent.id, { + borderColor: e.target.value, + }) + } + className="h-8" + /> +
+
+
{/* 데이터 바인딩 (텍스트/라벨/테이블 컴포넌트) */} diff --git a/frontend/components/report/designer/ReportPreviewModal.tsx b/frontend/components/report/designer/ReportPreviewModal.tsx index f64daaea..f271a003 100644 --- a/frontend/components/report/designer/ReportPreviewModal.tsx +++ b/frontend/components/report/designer/ReportPreviewModal.tsx @@ -104,6 +104,11 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) top: `${component.y}px`, width: `${component.width}px`, height: `${component.height}px`, + backgroundColor: component.backgroundColor, + border: component.borderWidth + ? `${component.borderWidth}px solid ${component.borderColor}` + : "none", + padding: "8px", }} > {component.type === "text" && ( @@ -111,7 +116,8 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) style={{ fontSize: `${component.fontSize}px`, color: component.fontColor, - backgroundColor: component.backgroundColor, + fontWeight: component.fontWeight, + textAlign: component.textAlign as "left" | "center" | "right", }} > {displayValue} @@ -120,11 +126,11 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) {component.type === "label" && (
{displayValue}