);
}
```
#### 4.2 툴바에 그리드 토글 추가
- **파일**: `frontend/components/report/designer/ReportDesignerToolbar.tsx`
- **내용**:
- 그리드 표시/숨김 버튼
- 그리드 설정 모달 열기 버튼
- 키보드 단축키 (`G` 키로 그리드 토글)
### Phase 5: Word 변환 개선
#### 5.1 그리드 기반 레이아웃 변환
- **파일**: `frontend/components/report/designer/ReportPreviewModal.tsx`
- **내용**:
- 그리드 정보를 활용하여 더 정확한 테이블 레이아웃 생성
- 그리드 행/열을 Word 테이블의 행/열로 매핑
```typescript
const handleDownloadWord = async () => {
// 그리드 기반으로 컴포넌트 배치 맵 생성
const gridMap: (ComponentConfig | null)[][] = Array(gridConfig.rows)
.fill(null)
.map(() => Array(gridConfig.columns).fill(null));
// 각 컴포넌트를 그리드 맵에 배치
for (const component of components) {
const gridX = Math.round(component.x / gridConfig.cellWidth);
const gridY = Math.round(component.y / gridConfig.cellHeight);
const gridWidth = Math.round(component.width / gridConfig.cellWidth);
const gridHeight = Math.round(component.height / gridConfig.cellHeight);
// 컴포넌트가 차지하는 모든 셀에 참조 저장
for (let y = gridY; y < gridY + gridHeight; y++) {
for (let x = gridX; x < gridX + gridWidth; x++) {
if (y < gridConfig.rows && x < gridConfig.columns) {
gridMap[y][x] = component;
}
}
}
}
// 그리드 맵을 Word 테이블로 변환
const tableRows: TableRow[] = [];
for (let y = 0; y < gridConfig.rows; y++) {
const cells: TableCell[] = [];
let x = 0;
while (x < gridConfig.columns) {
const component = gridMap[y][x];
if (!component) {
// 빈 셀
cells.push(new TableCell({ children: [new Paragraph("")] }));
x++;
} else {
// 컴포넌트 셀
const gridWidth = Math.round(component.width / gridConfig.cellWidth);
const gridHeight = Math.round(component.height / gridConfig.cellHeight);
const cell = createTableCell(component, gridWidth, gridHeight);
if (cell) cells.push(cell);
x += gridWidth;
}
}
if (cells.length > 0) {
tableRows.push(new TableRow({ children: cells }));
}
}
// ... Word 문서 생성
};
```
### Phase 6: 데이터 마이그레이션
#### 6.1 기존 레이아웃 자동 변환
- **파일**: `frontend/lib/utils/layoutMigration.ts` (신규)
- **내용**:
- 기존 절대 위치 데이터를 그리드 기반으로 변환
- 가장 가까운 그리드 셀에 스냅
- 마이그레이션 로그 생성
```typescript
export function migrateLayoutToGrid(
layout: ReportLayoutConfig,
gridConfig: GridConfig
): ReportLayoutConfig {
return {
...layout,
pages: layout.pages.map((page) => ({
...page,
gridConfig,
components: page.components.map((component) => {
// 픽셀 좌표를 그리드 좌표로 변환
const gridX = Math.round(component.x / gridConfig.cellWidth);
const gridY = Math.round(component.y / gridConfig.cellHeight);
const gridWidth = Math.max(
1,
Math.round(component.width / gridConfig.cellWidth)
);
const gridHeight = Math.max(
1,
Math.round(component.height / gridConfig.cellHeight)
);
return {
...component,
gridX,
gridY,
gridWidth,
gridHeight,
x: gridX * gridConfig.cellWidth,
y: gridY * gridConfig.cellHeight,
width: gridWidth * gridConfig.cellWidth,
height: gridHeight * gridConfig.cellHeight,
};
}),
})),
};
}
```
#### 6.2 마이그레이션 UI
- **파일**: `frontend/components/report/designer/MigrationModal.tsx` (신규)
- **내용**:
- 기존 리포트 로드 시 마이그레이션 필요 여부 체크
- 마이그레이션 전/후 미리보기
- 사용자 확인 후 적용
## 데이터베이스 스키마 변경
### report_layout_pages 테이블
```sql
ALTER TABLE report_layout_pages
ADD COLUMN grid_cell_width INTEGER DEFAULT 20,
ADD COLUMN grid_cell_height INTEGER DEFAULT 20,
ADD COLUMN grid_visible BOOLEAN DEFAULT true,
ADD COLUMN grid_snap_enabled BOOLEAN DEFAULT true,
ADD COLUMN grid_color VARCHAR(7) DEFAULT '#e5e7eb',
ADD COLUMN grid_opacity DECIMAL(3,2) DEFAULT 0.5;
```
### report_layout_components 테이블
```sql
ALTER TABLE report_layout_components
ADD COLUMN grid_x INTEGER,
ADD COLUMN grid_y INTEGER,
ADD COLUMN grid_width INTEGER,
ADD COLUMN grid_height INTEGER;
-- 기존 데이터 마이그레이션
UPDATE report_layout_components
SET
grid_x = ROUND(position_x / 20.0),
grid_y = ROUND(position_y / 20.0),
grid_width = GREATEST(1, ROUND(width / 20.0)),
grid_height = GREATEST(1, ROUND(height / 20.0))
WHERE grid_x IS NULL;
```
## 테스트 계획
### 단위 테스트
- `gridUtils.ts`의 모든 함수 테스트
- 그리드 좌표 ↔ 픽셀 좌표 변환 정확성
- 충돌 감지 로직
### 통합 테스트
- 드래그 앤 드롭 시 그리드 스냅 동작
- 리사이즈 시 그리드 스냅 동작
- 그리드 크기 변경 시 컴포넌트 재배치
### E2E 테스트
- 새 리포트 생성 및 그리드 설정
- 기존 리포트 마이그레이션
- Word 다운로드 시 레이아웃 정확성
## 예상 개발 일정
- **Phase 1**: 그리드 시스템 기반 구조 (2일)
- **Phase 2**: 그리드 시각화 (1일)
- **Phase 3**: 드래그 앤 드롭 스냅 (2일)
- **Phase 4**: 그리드 설정 UI (1일)
- **Phase 5**: Word 변환 개선 (2일)
- **Phase 6**: 데이터 마이그레이션 (1일)
- **테스트 및 디버깅**: (2일)
**총 예상 기간**: 11일
## 기술적 고려사항
### 성능 최적화
- 그리드 렌더링: SVG 대신 Canvas API 고려 (많은 셀의 경우)
- 메모이제이션: 그리드 계산 결과 캐싱
- 가상화: 큰 페이지에서 보이는 영역만 렌더링
### 사용자 경험
- 실시간 스냅 가이드: 드래그 중 스냅될 위치 미리 표시
- 키보드 단축키: 방향키로 그리드 단위 이동, Shift+방향키로 픽셀 단위 미세 조정
- 언두/리두: 그리드 스냅 적용 전/후 상태 저장
### 하위 호환성
- 기존 리포트는 자동 마이그레이션 제공
- 마이그레이션 옵션: 자동 / 수동 선택 가능
- 레거시 모드: 그리드 없이 자유 배치 가능 (옵션)
## 추가 기능 (향후 확장)
### 스마트 가이드
- 다른 컴포넌트와 정렬 시 가이드 라인 표시
- 균등 간격 가이드
### 그리드 템플릿
- 자주 사용하는 그리드 레이아웃 템플릿 제공
- 문서 종류별 프리셋 (계약서, 보고서, 송장 등)
### 그리드 병합
- 여러 그리드 셀을 하나로 병합
- 복잡한 레이아웃 지원
## 참고 자료
- Android Home Screen Widget System
- Microsoft Word Table Layout
- CSS Grid Layout
- Figma Auto Layout