diff --git a/backend-node/src/controllers/reportController.ts b/backend-node/src/controllers/reportController.ts index 5f755947..c6605d3e 100644 --- a/backend-node/src/controllers/reportController.ts +++ b/backend-node/src/controllers/reportController.ts @@ -3067,8 +3067,8 @@ export class ReportController { children.push(new Paragraph({ children: [] })); } - // 워터마크 헤더 생성 (워터마크가 활성화된 경우) - const watermark: WatermarkConfig | undefined = page.watermark; + // 워터마크 헤더 생성 (전체 페이지 공유 워터마크) + const watermark: WatermarkConfig | undefined = layoutConfig.watermark; let headers: { default?: Header } | undefined; if (watermark?.enabled && watermark.type === "text" && watermark.text) { diff --git a/backend-node/src/types/report.ts b/backend-node/src/types/report.ts index d5641cff..27254b0d 100644 --- a/backend-node/src/types/report.ts +++ b/backend-node/src/types/report.ts @@ -147,12 +147,12 @@ export interface PageConfig { right: number; }; components: any[]; - watermark?: WatermarkConfig; } // 레이아웃 설정 export interface ReportLayoutConfig { pages: PageConfig[]; + watermark?: WatermarkConfig; // 전체 페이지 공유 워터마크 } // 레이아웃 저장 요청 diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx index 7f63123b..85dc89b8 100644 --- a/frontend/components/report/designer/ReportDesignerCanvas.tsx +++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx @@ -213,6 +213,7 @@ export function ReportDesignerCanvas() { undo, redo, showRuler, + layoutConfig, } = useReportDesigner(); const [{ isOver }, drop] = useDrop(() => ({ @@ -608,10 +609,10 @@ export function ReportDesignerCanvas() { /> )} - {/* 워터마크 렌더링 */} - {currentPage?.watermark?.enabled && ( + {/* 워터마크 렌더링 (전체 페이지 공유) */} + {layoutConfig.watermark?.enabled && ( diff --git a/frontend/components/report/designer/ReportDesignerRightPanel.tsx b/frontend/components/report/designer/ReportDesignerRightPanel.tsx index da28fdfe..e3a24025 100644 --- a/frontend/components/report/designer/ReportDesignerRightPanel.tsx +++ b/frontend/components/report/designer/ReportDesignerRightPanel.tsx @@ -31,6 +31,8 @@ export function ReportDesignerRightPanel() { currentPageId, updatePageSettings, getQueryResult, + layoutConfig, + updateWatermark, } = context; const [activeTab, setActiveTab] = useState("properties"); const [uploadingImage, setUploadingImage] = useState(false); @@ -101,7 +103,7 @@ export function ReportDesignerRightPanel() { // 워터마크 이미지 업로드 핸들러 const handleWatermarkImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (!file || !currentPageId) return; + if (!file) return; // 파일 타입 체크 if (!file.type.startsWith("image/")) { @@ -129,12 +131,10 @@ export function ReportDesignerRightPanel() { const result = await reportApi.uploadImage(file); if (result.success) { - // 업로드된 이미지 URL을 워터마크에 설정 - updatePageSettings(currentPageId, { - watermark: { - ...currentPage!.watermark!, - imageUrl: result.data.fileUrl, - }, + // 업로드된 이미지 URL을 전체 워터마크에 설정 + updateWatermark({ + ...layoutConfig.watermark!, + imageUrl: result.data.fileUrl, }); toast({ @@ -2690,44 +2690,40 @@ export function ReportDesignerRightPanel() { - {/* 워터마크 설정 */} + {/* 워터마크 설정 (전체 페이지 공유) */} - 워터마크 + 워터마크 (전체 페이지) {/* 워터마크 활성화 */}
- updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark, - enabled: checked, - type: currentPage.watermark?.type ?? "text", - opacity: currentPage.watermark?.opacity ?? 0.3, - style: currentPage.watermark?.style ?? "diagonal", - }, + updateWatermark({ + ...layoutConfig.watermark, + enabled: checked, + type: layoutConfig.watermark?.type ?? "text", + opacity: layoutConfig.watermark?.opacity ?? 0.3, + style: layoutConfig.watermark?.style ?? "diagonal", }) } />
- {currentPage.watermark?.enabled && ( + {layoutConfig.watermark?.enabled && ( <> {/* 워터마크 타입 */}
- updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - text: e.target.value, - }, + updateWatermark({ + ...layoutConfig.watermark!, + text: e.target.value, }) } placeholder="DRAFT, 대외비 등" @@ -2765,13 +2759,11 @@ export function ReportDesignerRightPanel() { - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - fontSize: Number(e.target.value), - }, + updateWatermark({ + ...layoutConfig.watermark!, + fontSize: Number(e.target.value), }) } className="mt-1" @@ -2784,26 +2776,22 @@ export function ReportDesignerRightPanel() {
- updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - fontColor: e.target.value, - }, + updateWatermark({ + ...layoutConfig.watermark!, + fontColor: e.target.value, }) } className="h-9 w-12 cursor-pointer p-1" /> - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - fontColor: e.target.value, - }, + updateWatermark({ + ...layoutConfig.watermark!, + fontColor: e.target.value, }) } className="flex-1" @@ -2815,7 +2803,7 @@ export function ReportDesignerRightPanel() { )} {/* 이미지 워터마크 설정 */} - {currentPage.watermark?.type === "image" && ( + {layoutConfig.watermark?.type === "image" && (
@@ -2843,21 +2831,19 @@ export function ReportDesignerRightPanel() { ) : ( <> - {currentPage.watermark?.imageUrl ? "이미지 변경" : "이미지 선택"} + {layoutConfig.watermark?.imageUrl ? "이미지 변경" : "이미지 선택"} )} - {currentPage.watermark?.imageUrl && ( + {layoutConfig.watermark?.imageUrl && (
@@ -2880,13 +2866,11 @@ export function ReportDesignerRightPanel() {
- updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - rotation: Number(e.target.value), - }, + updateWatermark({ + ...layoutConfig.watermark!, + rotation: Number(e.target.value), }) } className="mt-1" @@ -2929,17 +2911,15 @@ export function ReportDesignerRightPanel() {
- {Math.round((currentPage.watermark?.opacity ?? 0.3) * 100)}% + {Math.round((layoutConfig.watermark?.opacity ?? 0.3) * 100)}%
- updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - opacity: value[0] / 100, - }, + updateWatermark({ + ...layoutConfig.watermark!, + opacity: value[0] / 100, }) } min={5} @@ -2955,17 +2935,15 @@ export function ReportDesignerRightPanel() { size="sm" variant="outline" onClick={() => - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - type: "text", - text: "DRAFT", - fontSize: 64, - fontColor: "#cccccc", - style: "diagonal", - opacity: 0.2, - rotation: -45, - }, + updateWatermark({ + ...layoutConfig.watermark!, + type: "text", + text: "DRAFT", + fontSize: 64, + fontColor: "#cccccc", + style: "diagonal", + opacity: 0.2, + rotation: -45, }) } > @@ -2975,17 +2953,15 @@ export function ReportDesignerRightPanel() { size="sm" variant="outline" onClick={() => - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - type: "text", - text: "대외비", - fontSize: 64, - fontColor: "#ff0000", - style: "diagonal", - opacity: 0.15, - rotation: -45, - }, + updateWatermark({ + ...layoutConfig.watermark!, + type: "text", + text: "대외비", + fontSize: 64, + fontColor: "#ff0000", + style: "diagonal", + opacity: 0.15, + rotation: -45, }) } > @@ -2995,17 +2971,15 @@ export function ReportDesignerRightPanel() { size="sm" variant="outline" onClick={() => - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - type: "text", - text: "SAMPLE", - fontSize: 48, - fontColor: "#888888", - style: "tile", - opacity: 0.1, - rotation: -30, - }, + updateWatermark({ + ...layoutConfig.watermark!, + type: "text", + text: "SAMPLE", + fontSize: 48, + fontColor: "#888888", + style: "tile", + opacity: 0.1, + rotation: -30, }) } > @@ -3015,16 +2989,14 @@ export function ReportDesignerRightPanel() { size="sm" variant="outline" onClick={() => - updatePageSettings(currentPageId, { - watermark: { - ...currentPage.watermark!, - type: "text", - text: "COPY", - fontSize: 56, - fontColor: "#aaaaaa", - style: "center", - opacity: 0.25, - }, + updateWatermark({ + ...layoutConfig.watermark!, + type: "text", + text: "COPY", + fontSize: 56, + fontColor: "#aaaaaa", + style: "center", + opacity: 0.25, }) } > diff --git a/frontend/components/report/designer/ReportPreviewModal.tsx b/frontend/components/report/designer/ReportPreviewModal.tsx index 92043666..b8fcb9ce 100644 --- a/frontend/components/report/designer/ReportPreviewModal.tsx +++ b/frontend/components/report/designer/ReportPreviewModal.tsx @@ -924,7 +924,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) page.background_color, pageIndex, totalPages, - page.watermark, + layoutConfig.watermark, // 전체 페이지 공유 워터마크 ), ) .join('
'); @@ -1156,10 +1156,10 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) backgroundColor: page.background_color, }} > - {/* 워터마크 렌더링 */} - {page.watermark?.enabled && ( + {/* 워터마크 렌더링 (전체 페이지 공유) */} + {layoutConfig.watermark?.enabled && ( diff --git a/frontend/contexts/ReportDesignerContext.tsx b/frontend/contexts/ReportDesignerContext.tsx index 206f56db..f8764d15 100644 --- a/frontend/contexts/ReportDesignerContext.tsx +++ b/frontend/contexts/ReportDesignerContext.tsx @@ -1,7 +1,7 @@ "use client"; import { createContext, useContext, useState, useCallback, ReactNode, useEffect, useRef } from "react"; -import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig } from "@/types/report"; +import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig, WatermarkConfig } from "@/types/report"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; import { v4 as uuidv4 } from "uuid"; @@ -40,6 +40,7 @@ interface ReportDesignerContextType { reorderPages: (sourceIndex: number, targetIndex: number) => void; selectPage: (pageId: string) => void; updatePageSettings: (pageId: string, settings: Partial) => void; + updateWatermark: (watermark: WatermarkConfig | undefined) => void; // 전체 페이지 공유 워터마크 // 컴포넌트 (현재 페이지) components: ComponentConfig[]; // currentPage의 components (읽기 전용) @@ -988,10 +989,19 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin const updatePageSettings = useCallback((pageId: string, settings: Partial) => { setLayoutConfig((prev) => ({ + ...prev, pages: prev.pages.map((page) => (page.page_id === pageId ? { ...page, ...settings } : page)), })); }, []); + // 전체 페이지 공유 워터마크 업데이트 + const updateWatermark = useCallback((watermark: WatermarkConfig | undefined) => { + setLayoutConfig((prev) => ({ + ...prev, + watermark, + })); + }, []); + // 리포트 및 레이아웃 로드 const loadLayout = useCallback(async () => { setIsLoading(true); @@ -1471,6 +1481,7 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin reorderPages, selectPage, updatePageSettings, + updateWatermark, // 컴포넌트 (현재 페이지) components, diff --git a/frontend/types/report.ts b/frontend/types/report.ts index 6619b534..3631f831 100644 --- a/frontend/types/report.ts +++ b/frontend/types/report.ts @@ -113,12 +113,12 @@ export interface ReportPage { }; background_color: string; components: ComponentConfig[]; - watermark?: WatermarkConfig; } // 레이아웃 설정 (페이지 기반) export interface ReportLayoutConfig { pages: ReportPage[]; + watermark?: WatermarkConfig; // 전체 페이지 공유 워터마크 } // 컴포넌트 설정