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}