From 27e33e27d1a7283512ff03ed6fd4d17fbc8a93c9 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Thu, 2 Oct 2025 14:13:11 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/designer/PageListPanel.tsx | 139 +++++++----------- frontend/contexts/ReportDesignerContext.tsx | 51 +++++-- 2 files changed, 92 insertions(+), 98 deletions(-) diff --git a/frontend/components/report/designer/PageListPanel.tsx b/frontend/components/report/designer/PageListPanel.tsx index dbe2cb71..e350ce51 100644 --- a/frontend/components/report/designer/PageListPanel.tsx +++ b/frontend/components/report/designer/PageListPanel.tsx @@ -86,34 +86,40 @@ export function PageListPanel() { {/* 페이지 목록 */} - -
- {layoutConfig.pages - .sort((a, b) => a.page_order - b.page_order) - .map((page, index) => ( - handleDragStart(index)} - onDragOver={(e) => handleDragOver(e, index)} - onDrop={(e) => handleDrop(e, index)} - onDragEnd={handleDragEnd} - className={`group relative cursor-pointer transition-all hover:shadow-md ${ - page.page_id === currentPageId - ? "border-primary bg-primary/5 ring-primary/20 ring-2" - : "border-border hover:border-primary/50" - } ${draggedIndex === index ? "opacity-50" : ""}`} - onClick={() => selectPage(page.page_id)} - > -
- {/* 드래그 핸들 & 페이지 정보 */} -
-
- +
+ +
+ {layoutConfig.pages + .sort((a, b) => a.page_order - b.page_order) + .map((page, index) => ( +
selectPage(page.page_id)} + onDragOver={(e) => handleDragOver(e, index)} + onDrop={(e) => handleDrop(e, index)} + > +
+ {/* 드래그 핸들 */} +
{ + e.stopPropagation(); + handleDragStart(index); + }} + onDragEnd={handleDragEnd} + className="text-muted-foreground cursor-grab opacity-0 transition-opacity group-hover:opacity-100 active:cursor-grabbing" + onClick={(e) => e.stopPropagation()} + > +
+ {/* 페이지 정보 */}
- {/* 페이지 이름 편집 */} {editingPageId === page.page_id ? (
e.stopPropagation()}> - -
) : ( -
- {page.page_name} - -
+
{page.page_name}
)} - - {/* 페이지 정보 */} -
- - {page.width} x {page.height}mm - - - {page.components.length}개 컴포넌트 +
+ {page.width}x{page.height}mm • {page.components.length}개
@@ -166,20 +153,29 @@ export function PageListPanel() { + { + e.stopPropagation(); + handleStartEdit(page.page_id, page.page_name); + }} + > + + 이름 변경 + { e.stopPropagation(); duplicatePage(page.page_id); }} > - + 복제 - + 삭제
- - {/* 썸네일 (간단한 미리보기) */} -
-
-
- {/* 간단한 컴포넌트 표시 */} - {page.components.slice(0, 10).map((comp) => ( -
- ))} -
-
-
- - ))} -
- + ))} +
+ +
{/* 푸터 */}
diff --git a/frontend/contexts/ReportDesignerContext.tsx b/frontend/contexts/ReportDesignerContext.tsx index f3bd68dc..1f58eea6 100644 --- a/frontend/contexts/ReportDesignerContext.tsx +++ b/frontend/contexts/ReportDesignerContext.tsx @@ -1,6 +1,6 @@ "use client"; -import { createContext, useContext, useState, useCallback, ReactNode, useEffect } from "react"; +import { createContext, useContext, useState, useCallback, ReactNode, useEffect, useRef } from "react"; import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig } from "@/types/report"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; @@ -165,23 +165,48 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin // 현재 페이지의 컴포넌트 (읽기 전용) const components = currentPage?.components || []; + // currentPageId를 ref로 저장하여 클로저 문제 해결 + const currentPageIdRef = useRef(currentPageId); + useEffect(() => { + currentPageIdRef.current = currentPageId; + }, [currentPageId]); + // 현재 페이지의 컴포넌트를 업데이트하는 헬퍼 함수 const setComponents = useCallback( (updater: ComponentConfig[] | ((prev: ComponentConfig[]) => ComponentConfig[])) => { - if (!currentPageId) return; + setLayoutConfig((prev) => { + const pageId = currentPageIdRef.current; + if (!pageId) { + console.error("❌ currentPageId가 없음"); + return prev; + } - setLayoutConfig((prev) => ({ - pages: prev.pages.map((page) => - page.page_id === currentPageId - ? { - ...page, - components: typeof updater === "function" ? updater(page.components) : updater, - } - : page, - ), - })); + // 현재 선택된 페이지 찾기 + const currentPageIndex = prev.pages.findIndex((p) => p.page_id === pageId); + if (currentPageIndex === -1) { + console.error("❌ 페이지를 찾을 수 없음:", pageId); + return prev; + } + + const currentPageData = prev.pages[currentPageIndex]; + const newComponents = typeof updater === "function" ? updater(currentPageData.components) : updater; + + const newPages = [...prev.pages]; + newPages[currentPageIndex] = { + ...currentPageData, + components: newComponents, + }; + + console.log("✅ 컴포넌트 업데이트:", { + pageId, + before: currentPageData.components.length, + after: newComponents.length, + }); + + return { pages: newPages }; + }); }, - [currentPageId], + [], // ref를 사용하므로 의존성 배열 비움 ); // 레이아웃 도구 설정