# 대시보드 그리드 시스템

## 개요

대시보드 캔버스는 **12 컬럼 그리드 시스템**을 사용하여 요소를 정렬하고 배치합니다.
모든 요소는 드래그 또는 리사이즈 종료 시 자동으로 그리드에 스냅됩니다.

## 그리드 설정

### 기본 설정 (`gridUtils.ts`)

```typescript
GRID_CONFIG = {
  COLUMNS: 12, // 12 컬럼
  CELL_SIZE: 60, // 60px x 60px 정사각형 셀
  GAP: 8, // 셀 간격 8px
  SNAP_THRESHOLD: 15, // 스냅 임계값 15px
};
```

### 실제 그리드 크기

- **셀 크기 (gap 포함)**: 68px (60px + 8px)
- **전체 캔버스 너비**: 808px (12 \* 68px - 8px)
- **셀 비율**: 1:1 (정사각형)

## 스냅 기능

### 1. 위치 스냅

요소를 드래그하여 이동할 때:

- **드래그 중**: 자유롭게 이동 (그리드 무시)
- **드래그 종료**: 가장 가까운 그리드 포인트에 자동 스냅
- **스냅 계산**: `Math.round(value / 68) * 68`

### 2. 크기 스냅

요소의 크기를 조절할 때:

- **리사이즈 중**: 자유롭게 크기 조절
- **리사이즈 종료**: 그리드 단위로 스냅
- **최소 크기**: 2셀 x 2셀 (136px x 136px)

### 3. 드롭 스냅

사이드바에서 새 요소를 드래그 앤 드롭할 때:

- 드롭 위치가 자동으로 가장 가까운 그리드 포인트에 스냅
- 기본 크기:
  - 차트: 4 x 3 셀 (264px x 196px)
  - 위젯: 2 x 2 셀 (136px x 136px)

## 시각적 피드백

### 그리드 배경

캔버스 배경에 그리드 라인이 표시됩니다:

```typescript
backgroundImage: `
  linear-gradient(rgba(59, 130, 246, 0.1) 1px, transparent 1px),
  linear-gradient(90deg, rgba(59, 130, 246, 0.1) 1px, transparent 1px)
`,
backgroundSize: '68px 68px'
```

### 요소 테두리

- **선택 안 됨**: 회색 테두리
- **선택됨**: 파란색 테두리 + 리사이즈 핸들 표시
- **드래그/리사이즈 중**: 트랜지션 비활성화 (부드러운 움직임)

## 사용 예시

### 기본 사용

```typescript
import { snapToGrid, snapSizeToGrid } from "./gridUtils";

// 위치 스냅
const snappedX = snapToGrid(123); // 136 (가장 가까운 그리드)
const snappedY = snapToGrid(45); // 68

// 크기 스냅
const snappedWidth = snapSizeToGrid(250); // 264 (4셀)
const snappedHeight = snapSizeToGrid(180); // 196 (3셀)
```

### 경계 체크와 함께

```typescript
import { snapBoundsToGrid } from "./gridUtils";

const snapped = snapBoundsToGrid(
  {
    position: { x: 123, y: 45 },
    size: { width: 250, height: 180 },
  },
  canvasWidth,
  canvasHeight,
);

// 결과:
// {
//   position: { x: 136, y: 68 },
//   size: { width: 264, height: 196 }
// }
```

## 그리드 인덱스

### 좌표 → 인덱스

```typescript
import { getGridIndex } from "./gridUtils";

const colIndex = getGridIndex(150); // 2 (3번째 컬럼)
const rowIndex = getGridIndex(100); // 1 (2번째 행)
```

### 인덱스 → 좌표

```typescript
import { gridIndexToCoordinate } from "./gridUtils";

const x = gridIndexToCoordinate(0); // 0   (1번째 컬럼)
const y = gridIndexToCoordinate(1); // 68  (2번째 행)
const z = gridIndexToCoordinate(11); // 748 (12번째 컬럼)
```

## 레이아웃 권장사항

### 일반 차트

- **권장 크기**: 4 x 3 셀 (264px x 196px)
- **최소 크기**: 2 x 2 셀 (136px x 136px)
- **최대 크기**: 12 x 8 셀 (808px x 536px)

### 작은 위젯

- **권장 크기**: 2 x 2 셀 (136px x 136px)
- **최소 크기**: 2 x 2 셀
- **최대 크기**: 4 x 4 셀 (264px x 264px)

### 큰 차트/대시보드

- **권장 크기**: 6 x 4 셀 (400px x 264px)
- **풀 너비**: 12 셀 (808px)

## 커스터마이징

### 그리드 크기 변경

`gridUtils.ts`의 `GRID_CONFIG`를 수정:

```typescript
export const GRID_CONFIG = {
  COLUMNS: 12,
  CELL_SIZE: 80, // 60 → 80 (셀 크기 증가)
  GAP: 16, // 8 → 16 (간격 증가)
  SNAP_THRESHOLD: 20, // 15 → 20 (스냅 범위 증가)
} as const;
```

### 스냅 비활성화

특정 요소에서 스냅을 비활성화하려면:

```typescript
// 드래그 종료 시 스냅하지 않고 그냥 업데이트
onUpdate(element.id, {
  position: { x: rawX, y: rawY }, // snapToGrid 호출 안 함
});
```

## 성능 최적화

### 트랜지션 제어

드래그/리사이즈 중에는 CSS 트랜지션을 비활성화:

```typescript
className={`
  ${(isDragging || isResizing) ? 'transition-none' : 'transition-all duration-150'}
`}
```

### 임시 상태 사용

마우스 이동 중에는 임시 위치/크기만 업데이트하고,
마우스 업 시에만 실제 스냅된 값으로 업데이트:

```typescript
// 드래그 중
setTempPosition({ x: rawX, y: rawY });

// 드래그 종료
const snapped = snapToGrid(tempPosition.x);
onUpdate(element.id, { position: { x: snapped, y: snapped } });
```

## 문제 해결

### 요소가 스냅되지 않는 경우

1. `snapToGrid` 함수가 호출되는지 확인
2. `SNAP_THRESHOLD` 값 확인 (너무 작으면 스냅 안 됨)
3. 임시 상태가 제대로 초기화되는지 확인

### 그리드가 보이지 않는 경우

1. 캔버스의 `backgroundImage` 스타일 확인
2. `getCellWithGap()` 반환값 확인
3. 브라우저 개발자 도구에서 배경 스타일 검사

### 성능 문제

1. 트랜지션이 비활성화되었는지 확인
2. 불필요한 리렌더링 방지 (React.memo 사용)
3. 마우스 이벤트 리스너가 제대로 제거되는지 확인

## 참고 자료

- **그리드 유틸리티**: `gridUtils.ts`
- **캔버스 컴포넌트**: `DashboardCanvas.tsx`
- **요소 컴포넌트**: `CanvasElement.tsx`
- **디자이너**: `DashboardDesigner.tsx`
