리포트 관리 되돌리기
This commit is contained in:
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user