ERP-node/frontend/lib/utils/gridUtils.ts

156 lines
4.5 KiB
TypeScript

import type { ComponentConfig, GridConfig } from "@/types/report";
/**
* 픽셀 좌표를 그리드 좌표로 변환
*/
export function pixelToGrid(pixel: number, cellSize: number): number {
return Math.round(pixel / cellSize);
}
/**
* 그리드 좌표를 픽셀 좌표로 변환
*/
export function gridToPixel(grid: number, cellSize: number): number {
return grid * cellSize;
}
/**
* 컴포넌트 위치/크기를 그리드에 스냅
*/
export function snapComponentToGrid(component: ComponentConfig, gridConfig: GridConfig): ComponentConfig {
if (!gridConfig.snapToGrid) {
return component;
}
// 픽셀 좌표를 그리드 좌표로 변환
const gridX = pixelToGrid(component.x, gridConfig.cellWidth);
const gridY = pixelToGrid(component.y, gridConfig.cellHeight);
const gridWidth = Math.max(1, pixelToGrid(component.width, gridConfig.cellWidth));
const gridHeight = Math.max(1, pixelToGrid(component.height, gridConfig.cellHeight));
// 그리드 좌표를 다시 픽셀로 변환
return {
...component,
gridX,
gridY,
gridWidth,
gridHeight,
x: gridToPixel(gridX, gridConfig.cellWidth),
y: gridToPixel(gridY, gridConfig.cellHeight),
width: gridToPixel(gridWidth, gridConfig.cellWidth),
height: gridToPixel(gridHeight, gridConfig.cellHeight),
};
}
/**
* 그리드 충돌 감지
* 두 컴포넌트가 겹치는지 확인
*/
export function detectGridCollision(
component: ComponentConfig,
otherComponents: ComponentConfig[],
gridConfig: GridConfig,
): boolean {
const comp1GridX = component.gridX ?? pixelToGrid(component.x, gridConfig.cellWidth);
const comp1GridY = component.gridY ?? pixelToGrid(component.y, gridConfig.cellHeight);
const comp1GridWidth = component.gridWidth ?? pixelToGrid(component.width, gridConfig.cellWidth);
const comp1GridHeight = component.gridHeight ?? pixelToGrid(component.height, gridConfig.cellHeight);
for (const other of otherComponents) {
if (other.id === component.id) continue;
const comp2GridX = other.gridX ?? pixelToGrid(other.x, gridConfig.cellWidth);
const comp2GridY = other.gridY ?? pixelToGrid(other.y, gridConfig.cellHeight);
const comp2GridWidth = other.gridWidth ?? pixelToGrid(other.width, gridConfig.cellWidth);
const comp2GridHeight = other.gridHeight ?? pixelToGrid(other.height, gridConfig.cellHeight);
// AABB (Axis-Aligned Bounding Box) 충돌 감지
const xOverlap = comp1GridX < comp2GridX + comp2GridWidth && comp1GridX + comp1GridWidth > comp2GridX;
const yOverlap = comp1GridY < comp2GridY + comp2GridHeight && comp1GridY + comp1GridHeight > comp2GridY;
if (xOverlap && yOverlap) {
return true;
}
}
return false;
}
/**
* 페이지 크기 기반 그리드 행/열 계산
*/
export function calculateGridDimensions(
pageWidth: number,
pageHeight: number,
cellWidth: number,
cellHeight: number,
): { rows: number; columns: number } {
return {
columns: Math.floor(pageWidth / cellWidth),
rows: Math.floor(pageHeight / cellHeight),
};
}
/**
* 기본 그리드 설정 생성
*/
export function createDefaultGridConfig(pageWidth: number, pageHeight: number): GridConfig {
const cellWidth = 20;
const cellHeight = 20;
const { rows, columns } = calculateGridDimensions(pageWidth, pageHeight, cellWidth, cellHeight);
return {
cellWidth,
cellHeight,
rows,
columns,
visible: true,
snapToGrid: true,
gridColor: "#e5e7eb",
gridOpacity: 0.5,
};
}
/**
* 위치가 페이지 경계 내에 있는지 확인
*/
export function isWithinPageBounds(
component: ComponentConfig,
pageWidth: number,
pageHeight: number,
margins: { top: number; bottom: number; left: number; right: number },
): boolean {
const minX = margins.left;
const minY = margins.top;
const maxX = pageWidth - margins.right;
const maxY = pageHeight - margins.bottom;
return (
component.x >= minX &&
component.y >= minY &&
component.x + component.width <= maxX &&
component.y + component.height <= maxY
);
}
/**
* 컴포넌트를 페이지 경계 내로 제한
*/
export function constrainToPageBounds(
component: ComponentConfig,
pageWidth: number,
pageHeight: number,
margins: { top: number; bottom: number; left: number; right: number },
): ComponentConfig {
const minX = margins.left;
const minY = margins.top;
const maxX = pageWidth - margins.right - component.width;
const maxY = pageHeight - margins.bottom - component.height;
return {
...component,
x: Math.max(minX, Math.min(maxX, component.x)),
y: Math.max(minY, Math.min(maxY, component.y)),
};
}