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;