리포트 관리 되돌리기

This commit is contained in:
dohyeons
2025-10-13 19:15:52 +09:00
parent a53940cff9
commit 28d460fecd
6 changed files with 37 additions and 222 deletions

View File

@@ -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<GridConfig>) => void;
// 캔버스 설정
canvasWidth: number;
canvasHeight: number;
@@ -226,50 +209,10 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
[], // ref를 사용하므로 의존성 배열 비움
);
// 그리드 설정
const [gridConfig, setGridConfig] = useState<GridConfig>(() => {
// 기본 페이지 크기 (A4: 794 x 1123 px at 96 DPI)
const defaultPageWidth = 794;
const defaultPageHeight = 1123;
return createDefaultGridConfig(defaultPageWidth, defaultPageHeight);
});
// gridConfig 업데이트 함수
const updateGridConfig = useCallback(
(updates: Partial<GridConfig>) => {
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<ComponentConfig>) => {
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 <ReportDesignerContext.Provider value={value}>{children}</ReportDesignerContext.Provider>;