diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index d56d07bb..31f12a32 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -32,7 +32,6 @@ import mailTemplateFileRoutes from "./routes/mailTemplateFileRoutes"; import mailAccountFileRoutes from "./routes/mailAccountFileRoutes"; import mailSendSimpleRoutes from "./routes/mailSendSimpleRoutes"; import mailReceiveBasicRoutes from "./routes/mailReceiveBasicRoutes"; -import mailSentHistoryRoutes from "./routes/mailSentHistoryRoutes"; import dataRoutes from "./routes/dataRoutes"; import testButtonDataflowRoutes from "./routes/testButtonDataflowRoutes"; import externalDbConnectionRoutes from "./routes/externalDbConnectionRoutes"; @@ -74,8 +73,8 @@ app.use( }) ); app.use(compression()); -app.use(express.json({ limit: "50mb" })); -app.use(express.urlencoded({ extended: true, limit: "50mb" })); +app.use(express.json({ limit: "10mb" })); +app.use(express.urlencoded({ extended: true, limit: "10mb" })); // 정적 파일 서빙 전에 CORS 미들웨어 추가 (OPTIONS 요청 처리) app.options("/uploads/*", (req, res) => { @@ -175,19 +174,7 @@ app.use("/api/layouts", layoutRoutes); app.use("/api/mail/accounts", mailAccountFileRoutes); // 파일 기반 계정 app.use("/api/mail/templates-file", mailTemplateFileRoutes); // 파일 기반 템플릿 app.use("/api/mail/send", mailSendSimpleRoutes); // 메일 발송 -// 메일 수신 라우트 디버깅 - 모든 요청 로깅 -app.use("/api/mail/receive", (req, res, next) => { - console.log(`\n🔍 [MAIL RECEIVE REQUEST]`); - console.log(` Method: ${req.method}`); - console.log(` URL: ${req.originalUrl}`); - console.log(` Path: ${req.path}`); - console.log(` Base URL: ${req.baseUrl}`); - console.log(` Params: ${JSON.stringify(req.params)}`); - console.log(` Query: ${JSON.stringify(req.query)}`); - next(); -}); app.use("/api/mail/receive", mailReceiveBasicRoutes); // 메일 수신 -app.use("/api/mail/sent", mailSentHistoryRoutes); // 발송 이력 app.use("/api/screen", screenStandardRoutes); app.use("/api/data", dataRoutes); app.use("/api/test-button-dataflow", testButtonDataflowRoutes); diff --git a/frontend/components/report/designer/ReportDesignerCanvas.tsx b/frontend/components/report/designer/ReportDesignerCanvas.tsx index de18d715..ace87249 100644 --- a/frontend/components/report/designer/ReportDesignerCanvas.tsx +++ b/frontend/components/report/designer/ReportDesignerCanvas.tsx @@ -6,7 +6,6 @@ import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import { ComponentConfig } from "@/types/report"; import { CanvasComponent } from "./CanvasComponent"; import { Ruler } from "./Ruler"; -import { GridLayer } from "./GridLayer"; import { v4 as uuidv4 } from "uuid"; export function ReportDesignerCanvas() { @@ -33,7 +32,6 @@ export function ReportDesignerCanvas() { undo, redo, showRuler, - gridConfig, } = useReportDesigner(); const [{ isOver }, drop] = useDrop(() => ({ @@ -333,16 +331,16 @@ export function ReportDesignerCanvas() { style={{ width: `${canvasWidth}mm`, minHeight: `${canvasHeight}mm`, + backgroundImage: showGrid + ? ` + linear-gradient(to right, #e5e7eb 1px, transparent 1px), + linear-gradient(to bottom, #e5e7eb 1px, transparent 1px) + ` + : undefined, + backgroundSize: showGrid ? `${gridSize}px ${gridSize}px` : undefined, }} onClick={handleCanvasClick} > - {/* 그리드 레이어 */} - - {/* 페이지 여백 가이드 */} {currentPage && (
- + 페이지 @@ -112,10 +111,6 @@ export function ReportDesignerRightPanel() { 속성 - - - 그리드 - 쿼리 @@ -1401,15 +1396,6 @@ export function ReportDesignerRightPanel() { {/* 쿼리 탭 */} - {/* 그리드 탭 */} - - -
- -
-
-
- diff --git a/frontend/contexts/ReportDesignerContext.tsx b/frontend/contexts/ReportDesignerContext.tsx index 8244cfd1..1f58eea6 100644 --- a/frontend/contexts/ReportDesignerContext.tsx +++ b/frontend/contexts/ReportDesignerContext.tsx @@ -1,23 +1,10 @@ "use client"; import { createContext, useContext, useState, useCallback, ReactNode, useEffect, useRef } from "react"; -import { - ComponentConfig, - ReportDetail, - ReportLayout, - ReportPage, - ReportLayoutConfig, - GridConfig, -} from "@/types/report"; +import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig } from "@/types/report"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; import { v4 as uuidv4 } from "uuid"; -import { - snapComponentToGrid, - createDefaultGridConfig, - calculateGridDimensions, - detectGridCollision, -} from "@/lib/utils/gridUtils"; export interface ReportQuery { id: string; @@ -84,10 +71,6 @@ interface ReportDesignerContextType { // 템플릿 적용 applyTemplate: (templateId: string) => void; - // 그리드 관리 - gridConfig: GridConfig; - updateGridConfig: (updates: Partial) => void; - // 캔버스 설정 canvasWidth: number; canvasHeight: number; @@ -226,50 +209,10 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin [], // ref를 사용하므로 의존성 배열 비움 ); - // 그리드 설정 - const [gridConfig, setGridConfig] = useState(() => { - // 기본 페이지 크기 (A4: 794 x 1123 px at 96 DPI) - const defaultPageWidth = 794; - const defaultPageHeight = 1123; - return createDefaultGridConfig(defaultPageWidth, defaultPageHeight); - }); - - // gridConfig 업데이트 함수 - const updateGridConfig = useCallback( - (updates: Partial) => { - setGridConfig((prev) => { - const newConfig = { ...prev, ...updates }; - - // cellWidth나 cellHeight가 변경되면 rows/columns 재계산 - if (updates.cellWidth || updates.cellHeight) { - const pageWidth = currentPage?.width ? currentPage.width * 3.7795275591 : 794; // mm to px - const pageHeight = currentPage?.height ? currentPage.height * 3.7795275591 : 1123; - const { rows, columns } = calculateGridDimensions( - pageWidth, - pageHeight, - newConfig.cellWidth, - newConfig.cellHeight, - ); - newConfig.rows = rows; - newConfig.columns = columns; - } - - return newConfig; - }); - }, - [currentPage], - ); - - // 레거시 호환성을 위한 별칭 - const gridSize = gridConfig.cellWidth; - const showGrid = gridConfig.visible; - const snapToGrid = gridConfig.snapToGrid; - const setGridSize = useCallback( - (size: number) => updateGridConfig({ cellWidth: size, cellHeight: size }), - [updateGridConfig], - ); - const setShowGrid = useCallback((visible: boolean) => updateGridConfig({ visible }), [updateGridConfig]); - const setSnapToGrid = useCallback((snap: boolean) => updateGridConfig({ snapToGrid: snap }), [updateGridConfig]); + // 레이아웃 도구 설정 + const [gridSize, setGridSize] = useState(10); // Grid Snap 크기 (px) + const [showGrid, setShowGrid] = useState(true); // Grid 표시 여부 + const [snapToGrid, setSnapToGrid] = useState(true); // Grid Snap 활성화 // 눈금자 표시 const [showRuler, setShowRuler] = useState(true); @@ -1235,23 +1178,9 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin // 컴포넌트 추가 (현재 페이지에) const addComponent = useCallback( (component: ComponentConfig) => { - // 그리드 스냅 적용 - const snappedComponent = snapComponentToGrid(component, gridConfig); - - // 충돌 감지 - const currentComponents = currentPage?.components || []; - if (detectGridCollision(snappedComponent, currentComponents, gridConfig)) { - toast({ - title: "경고", - description: "다른 컴포넌트와 겹칩니다. 다른 위치에 배치해주세요.", - variant: "destructive", - }); - return; - } - - setComponents((prev) => [...prev, snappedComponent]); + setComponents((prev) => [...prev, component]); }, - [setComponents, gridConfig, currentPage, toast], + [setComponents], ); // 컴포넌트 업데이트 (현재 페이지에서) @@ -1259,60 +1188,18 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin (id: string, updates: Partial) => { if (!currentPageId) return; - setLayoutConfig((prev) => { - let hasCollision = false; - - const newPages = prev.pages.map((page) => { - if (page.page_id !== currentPageId) return page; - - const newComponents = page.components.map((comp) => { - if (comp.id !== id) return comp; - - // 업데이트된 컴포넌트에 그리드 스냅 적용 - const updated = { ...comp, ...updates }; - - // 위치나 크기가 변경된 경우에만 스냅 적용 및 충돌 감지 - if ( - updates.x !== undefined || - updates.y !== undefined || - updates.width !== undefined || - updates.height !== undefined - ) { - const snapped = snapComponentToGrid(updated, gridConfig); - - // 충돌 감지 (자신을 제외한 다른 컴포넌트와) - const otherComponents = page.components.filter((c) => c.id !== id); - if (detectGridCollision(snapped, otherComponents, gridConfig)) { - hasCollision = true; - return comp; // 충돌 시 원래 상태 유지 + setLayoutConfig((prev) => ({ + pages: prev.pages.map((page) => + page.page_id === currentPageId + ? { + ...page, + components: page.components.map((comp) => (comp.id === id ? { ...comp, ...updates } : comp)), } - - return snapped; - } - - return updated; - }); - - return { - ...page, - components: newComponents, - }; - }); - - // 충돌이 감지된 경우 토스트 메시지 표시 및 업데이트 취소 - if (hasCollision) { - toast({ - title: "경고", - description: "다른 컴포넌트와 겹칩니다.", - variant: "destructive", - }); - return prev; - } - - return { pages: newPages }; - }); + : page, + ), + })); }, - [currentPageId, gridConfig, toast], + [currentPageId], ); // 컴포넌트 삭제 (현재 페이지에서) @@ -1426,36 +1313,14 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin window.history.replaceState({}, "", `/admin/report/designer/${actualReportId}`); } - // 백엔드 호환성을 위해 첫 번째 페이지 정보를 레거시 필드로 변환 - const firstPage = layoutConfig.pages[0]; - const legacyFormat = firstPage - ? { - canvasWidth: firstPage.width, - canvasHeight: firstPage.height, - pageOrientation: firstPage.orientation, - components: firstPage.components, - margins: firstPage.margins, - // 새로운 페이지 기반 구조도 함께 전송 - layoutConfig, - queries: queries.map((q) => ({ - ...q, - externalConnectionId: q.externalConnectionId || undefined, - })), - } - : { - canvasWidth: 210, - canvasHeight: 297, - pageOrientation: "portrait" as const, - components: [], - layoutConfig, - queries: queries.map((q) => ({ - ...q, - externalConnectionId: q.externalConnectionId || undefined, - })), - }; - - // 레이아웃 저장 - await reportApi.saveLayout(actualReportId, legacyFormat); + // 레이아웃 저장 (페이지 구조로) + await reportApi.saveLayout(actualReportId, { + layoutConfig, // 페이지 기반 구조 + queries: queries.map((q) => ({ + ...q, + externalConnectionId: q.externalConnectionId || undefined, + })), + }); toast({ title: "성공", @@ -1676,9 +1541,6 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin // 그룹화 groupComponents, ungroupComponents, - // 그리드 관리 - gridConfig, - updateGridConfig, }; return {children}; diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts index 1e37c955..9aa0eb02 100644 --- a/frontend/lib/api/client.ts +++ b/frontend/lib/api/client.ts @@ -12,12 +12,12 @@ const getApiBaseUrl = (): string => { const currentHost = window.location.hostname; const currentPort = window.location.port; - // 🎯 로컬 개발환경: Next.js 프록시 사용 (대용량 요청 안정성) + // 로컬 개발환경: localhost:9771 또는 localhost:3000 → localhost:8080 if ( (currentHost === "localhost" || currentHost === "127.0.0.1") && (currentPort === "9771" || currentPort === "3000") ) { - return "/api"; // 프록시 사용 + return "http://localhost:8080/api"; } } diff --git a/frontend/types/report.ts b/frontend/types/report.ts index 127a3a4c..2c720d77 100644 --- a/frontend/types/report.ts +++ b/frontend/types/report.ts @@ -81,18 +81,6 @@ export interface ExternalConnection { is_active: string; } -// 그리드 설정 -export interface GridConfig { - cellWidth: number; // 그리드 셀 너비 (px) - cellHeight: number; // 그리드 셀 높이 (px) - rows: number; // 세로 그리드 수 (계산값: pageHeight / cellHeight) - columns: number; // 가로 그리드 수 (계산값: pageWidth / cellHeight) - visible: boolean; // 그리드 표시 여부 - snapToGrid: boolean; // 그리드 스냅 활성화 여부 - gridColor: string; // 그리드 선 색상 - gridOpacity: number; // 그리드 투명도 (0-1) -} - // 페이지 설정 export interface ReportPage { page_id: string; @@ -108,7 +96,6 @@ export interface ReportPage { right: number; }; background_color: string; - gridConfig?: GridConfig; // 그리드 설정 (옵셔널) components: ComponentConfig[]; } @@ -126,11 +113,6 @@ export interface ComponentConfig { width: number; height: number; zIndex: number; - // 그리드 좌표 (옵셔널) - gridX?: number; // 시작 열 (0부터 시작) - gridY?: number; // 시작 행 (0부터 시작) - gridWidth?: number; // 차지하는 열 수 - gridHeight?: number; // 차지하는 행 수 fontSize?: number; fontFamily?: string; fontWeight?: string;