ERP-node/frontend/components/admin/dashboard/gridUtils.ts

170 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 대시보드 그리드 시스템 유틸리티
* - 12 컬럼 그리드 시스템
* - 정사각형 셀 (가로 = 세로)
* - 스냅 기능
*/
// 그리드 설정 (고정 크기)
export const GRID_CONFIG = {
COLUMNS: 12,
CELL_SIZE: 132, // 고정 셀 크기
GAP: 8, // 셀 간격
SNAP_THRESHOLD: 15, // 스냅 임계값 (px)
ELEMENT_PADDING: 4, // 요소 주위 여백 (px)
CANVAS_WIDTH: 1682, // 고정 캔버스 너비 (실제 측정값)
// 계산식: (132 + 8) × 12 - 8 = 1672px (그리드)
// 추가 여백 10px 포함 = 1682px
} as const;
/**
* 실제 그리드 셀 크기 계산 (gap 포함)
*/
export const getCellWithGap = () => {
return GRID_CONFIG.CELL_SIZE + GRID_CONFIG.GAP;
};
/**
* 전체 캔버스 너비 계산
*/
export const getCanvasWidth = () => {
const cellWithGap = getCellWithGap();
return GRID_CONFIG.COLUMNS * cellWithGap - GRID_CONFIG.GAP;
};
/**
* 좌표를 가장 가까운 그리드 포인트로 스냅 (여백 포함)
* @param value - 스냅할 좌표값
* @param cellSize - 셀 크기 (선택사항, 기본값은 GRID_CONFIG.CELL_SIZE)
* @returns 스냅된 좌표값 (여백 포함)
*/
export const snapToGrid = (value: number, cellSize: number = GRID_CONFIG.CELL_SIZE): number => {
const cellWithGap = cellSize + GRID_CONFIG.GAP;
const gridIndex = Math.round(value / cellWithGap);
return gridIndex * cellWithGap + GRID_CONFIG.ELEMENT_PADDING;
};
/**
* 좌표를 그리드에 스냅 (임계값 적용)
* @param value - 현재 좌표값
* @param cellSize - 셀 크기 (선택사항)
* @returns 스냅된 좌표값 (임계값 내에 있으면 스냅, 아니면 원래 값)
*/
export const snapToGridWithThreshold = (value: number, cellSize: number = GRID_CONFIG.CELL_SIZE): number => {
const snapped = snapToGrid(value, cellSize);
const distance = Math.abs(value - snapped);
return distance <= GRID_CONFIG.SNAP_THRESHOLD ? snapped : value;
};
/**
* 크기를 그리드 단위로 스냅
* @param size - 스냅할 크기
* @param minCells - 최소 셀 개수 (기본값: 2)
* @param cellSize - 셀 크기 (선택사항)
* @returns 스냅된 크기
*/
export const snapSizeToGrid = (
size: number,
minCells: number = 2,
cellSize: number = GRID_CONFIG.CELL_SIZE,
): number => {
const cellWithGap = cellSize + GRID_CONFIG.GAP;
const cells = Math.max(minCells, Math.round(size / cellWithGap));
return cells * cellWithGap - GRID_CONFIG.GAP;
};
/**
* 위치와 크기를 모두 그리드에 스냅
*/
export interface GridPosition {
x: number;
y: number;
}
export interface GridSize {
width: number;
height: number;
}
export interface GridBounds {
position: GridPosition;
size: GridSize;
}
/**
* 요소의 위치와 크기를 그리드에 맞춰 조정
* @param bounds - 현재 위치와 크기
* @param canvasWidth - 캔버스 너비 (경계 체크용)
* @param canvasHeight - 캔버스 높이 (경계 체크용)
* @returns 그리드에 스냅된 위치와 크기
*/
export const snapBoundsToGrid = (bounds: GridBounds, canvasWidth?: number, canvasHeight?: number): GridBounds => {
// 위치 스냅
let snappedX = snapToGrid(bounds.position.x);
let snappedY = snapToGrid(bounds.position.y);
// 크기 스냅
const snappedWidth = snapSizeToGrid(bounds.size.width);
const snappedHeight = snapSizeToGrid(bounds.size.height);
// 캔버스 경계 체크
if (canvasWidth) {
snappedX = Math.min(snappedX, canvasWidth - snappedWidth);
}
if (canvasHeight) {
snappedY = Math.min(snappedY, canvasHeight - snappedHeight);
}
// 음수 방지
snappedX = Math.max(0, snappedX);
snappedY = Math.max(0, snappedY);
return {
position: { x: snappedX, y: snappedY },
size: { width: snappedWidth, height: snappedHeight },
};
};
/**
* 좌표가 어느 그리드 셀에 속하는지 계산
* @param value - 좌표값
* @returns 그리드 인덱스 (0부터 시작)
*/
export const getGridIndex = (value: number): number => {
const cellWithGap = getCellWithGap();
return Math.floor(value / cellWithGap);
};
/**
* 그리드 인덱스를 좌표로 변환
* @param index - 그리드 인덱스
* @returns 좌표값
*/
export const gridIndexToCoordinate = (index: number): number => {
const cellWithGap = getCellWithGap();
return index * cellWithGap;
};
/**
* 스냅 가이드라인 표시용 좌표 계산
* @param value - 현재 좌표
* @returns 가장 가까운 그리드 라인들의 좌표 배열
*/
export const getNearbyGridLines = (value: number): number[] => {
const snapped = snapToGrid(value);
const cellWithGap = getCellWithGap();
return [snapped - cellWithGap, snapped, snapped + cellWithGap].filter((line) => line >= 0);
};
/**
* 위치가 스냅 임계값 내에 있는지 확인
* @param value - 현재 값
* @param snapValue - 스냅할 값
* @returns 임계값 내에 있으면 true
*/
export const isWithinSnapThreshold = (value: number, snapValue: number): boolean => {
return Math.abs(value - snapValue) <= GRID_CONFIG.SNAP_THRESHOLD;
};