ERP-node/popdocs/archive/GRID_SYSTEM_PLAN.md

13 KiB

POP 그리드 시스템 도입 계획

작성일: 2026-02-05 상태: 계획 승인, 구현 대기


개요

목표

현재 Flexbox 기반 v4 시스템을 CSS Grid 기반 v5 시스템으로 전환하여 4~14인치 화면에서 일관된 배치와 예측 가능한 반응형 레이아웃 구현

핵심 변경점

항목 v4 (현재) v5 (그리드)
배치 방식 Flexbox 흐름 Grid 위치 지정
크기 단위 픽셀 (200px) 칸 (col, row)
위치 지정 순서대로 자동 열/행 좌표
줄바꿈 자동 (넘치면) 명시적 (row 지정)

Phase 구조

[Phase 5.1]       [Phase 5.2]        [Phase 5.3]        [Phase 5.4]
그리드 타입 정의 → 그리드 렌더러   → 디자이너 UI    → 반응형 자동화
    1주            1주               1~2주              1주

Phase 5.1: 그리드 타입 정의

목표

v5 레이아웃 데이터 구조 설계

작업 항목

  • PopLayoutDataV5 인터페이스 정의
  • PopGridConfig 인터페이스 (그리드 설정)
  • PopComponentPositionV5 인터페이스 (위치: col, row, colSpan, rowSpan)
  • PopSizeConstraintV5 인터페이스 (칸 기반 크기)
  • 브레이크포인트 상수 정의
  • createEmptyPopLayoutV5() 생성 함수
  • isV5Layout() 타입 가드

데이터 구조 설계

// v5 레이아웃
interface PopLayoutDataV5 {
  version: "pop-5.0";
  
  // 그리드 설정
  gridConfig: PopGridConfig;
  
  // 컴포넌트 목록
  components: Record<string, PopComponentDefinitionV5>;
  
  // 모드별 오버라이드
  overrides?: {
    mobile_portrait?: PopModeOverrideV5;
    mobile_landscape?: PopModeOverrideV5;
    tablet_portrait?: PopModeOverrideV5;
  };
  
  // 기존 호환
  dataFlow: PopDataFlow;
  settings: PopGlobalSettingsV5;
}

// 그리드 설정
interface PopGridConfig {
  // 모드별 칸 수
  columns: {
    tablet_landscape: 12;   // 기본 (10~14인치)
    tablet_portrait: 8;     // 8~10인치 세로
    mobile_landscape: 6;    // 6~8인치 가로
    mobile_portrait: 4;     // 4~6인치 세로
  };
  
  // 행 높이 (px) - 1행의 기본 높이
  rowHeight: number;  // 기본 48px
  
  // 간격
  gap: number;        // 기본 8px
  padding: number;    // 기본 16px
}

// 컴포넌트 정의
interface PopComponentDefinitionV5 {
  id: string;
  type: PopComponentType;
  label?: string;
  
  // 위치 (열/행 좌표) - 기본 모드(태블릿 가로) 기준
  position: {
    col: number;      // 시작 열 (1부터)
    row: number;      // 시작 행 (1부터)
    colSpan: number;  // 차지할 열 수 (1~12)
    rowSpan: number;  // 차지할 행 수 (1~)
  };
  
  // 모드별 표시/숨김
  visibility?: {
    tablet_landscape?: boolean;
    tablet_portrait?: boolean;
    mobile_landscape?: boolean;
    mobile_portrait?: boolean;
  };
  
  // 기존 속성
  dataBinding?: PopDataBinding;
  config?: PopComponentConfig;
}

브레이크포인트 정의

// 브레이크포인트 상수
const GRID_BREAKPOINTS = {
  // 4~6인치 모바일 세로
  mobile_portrait: {
    maxWidth: 599,
    columns: 4,
    rowHeight: 40,
    gap: 8,
    padding: 12,
  },
  
  // 6~8인치 모바일 가로 / 작은 태블릿
  mobile_landscape: {
    minWidth: 600,
    maxWidth: 839,
    columns: 6,
    rowHeight: 44,
    gap: 8,
    padding: 16,
  },
  
  // 8~10인치 태블릿 세로
  tablet_portrait: {
    minWidth: 840,
    maxWidth: 1023,
    columns: 8,
    rowHeight: 48,
    gap: 12,
    padding: 16,
  },
  
  // 10~14인치 태블릿 가로 (기본)
  tablet_landscape: {
    minWidth: 1024,
    columns: 12,
    rowHeight: 48,
    gap: 16,
    padding: 24,
  },
} as const;

산출물

  • frontend/components/pop/designer/types/pop-layout-v5.ts

Phase 5.2: 그리드 렌더러

목표

CSS Grid 기반 렌더러 구현

작업 항목

  • PopGridRenderer.tsx 생성
  • CSS Grid 스타일 계산 로직
  • 브레이크포인트 감지 및 칸 수 자동 변경
  • 컴포넌트 위치 렌더링 (grid-column, grid-row)
  • 모드별 자동 위치 재계산 (12칸→4칸 변환)
  • visibility 처리
  • 기존 PopFlexRenderer와 공존

렌더링 로직

// CSS Grid 스타일 생성
function calculateGridStyle(config: PopGridConfig, mode: string): React.CSSProperties {
  const columns = config.columns[mode];
  const { rowHeight, gap, padding } = config;
  
  return {
    display: 'grid',
    gridTemplateColumns: `repeat(${columns}, 1fr)`,
    gridAutoRows: `${rowHeight}px`,
    gap: `${gap}px`,
    padding: `${padding}px`,
  };
}

// 컴포넌트 위치 스타일
function calculatePositionStyle(
  position: PopComponentPositionV5['position'],
  sourceColumns: number,  // 원본 모드 칸 수 (12)
  targetColumns: number   // 현재 모드 칸 수 (4)
): React.CSSProperties {
  // 12칸 → 4칸 변환 예시
  // col: 7, colSpan: 3 → col: 3, colSpan: 1
  const ratio = targetColumns / sourceColumns;
  const newCol = Math.max(1, Math.ceil(position.col * ratio));
  const newColSpan = Math.max(1, Math.round(position.colSpan * ratio));
  
  return {
    gridColumn: `${newCol} / span ${Math.min(newColSpan, targetColumns - newCol + 1)}`,
    gridRow: `${position.row} / span ${position.rowSpan}`,
  };
}

산출물

  • frontend/components/pop/designer/renderers/PopGridRenderer.tsx

Phase 5.3: 디자이너 UI

목표

그리드 기반 편집 UI 구현

작업 항목

  • PopCanvasV5.tsx 생성 (그리드 캔버스)
  • 그리드 배경 표시 (바둑판 모양)
  • 컴포넌트 드래그 배치 (칸에 스냅)
  • 컴포넌트 리사이즈 (칸 단위)
  • 위치 편집 패널 (col, row, colSpan, rowSpan)
  • 모드 전환 시 그리드 칸 수 변경 표시
  • v4/v5 자동 판별 및 전환

UI 구조

┌─────────────────────────────────────────────────────────────────┐
│ ← 목록  화면명  *변경됨   [↶][↷]  그리드 레이아웃 (v5)   [저장] │
├─────────────────────────────────────────────────────────────────┤
│ 미리보기: [모바일↕ 4칸] [모바일↔ 6칸] [태블릿↕ 8칸] [태블릿↔ 12칸] │
├────────────┬────────────────────────────────────┬───────────────┤
│            │  1   2   3   4   5   6   ...  12   │               │
│ 컴포넌트    │ ┌───────────┬───────────┐         │   위치        │
│            │1│    A      │     B     │         │   열: [1-12]  │
│ 필드       │ ├───────────┴───────────┤         │   행: [1-99]  │
│ 버튼       │2│          C            │         │   너비: [1-12]│
│ 리스트     │ ├───────────┬───────────┤         │   높이: [1-10]│
│ 인디케이터 │3│    D      │     E     │         │               │
│ ...        │ └───────────┴───────────┘         │   표시 설정   │
│            │                                    │   [x] 태블릿↔ │
│            │        (그리드 배경 표시)          │   [x] 모바일↕ │
└────────────┴────────────────────────────────────┴───────────────┘

드래그 앤 드롭 로직

// 마우스 위치 → 그리드 좌표 변환
function mouseToGridPosition(
  mouseX: number,
  mouseY: number,
  gridConfig: PopGridConfig,
  canvasRect: DOMRect
): { col: number; row: number } {
  const { columns, rowHeight, gap, padding } = gridConfig;
  
  // 캔버스 내 상대 위치
  const relX = mouseX - canvasRect.left - padding;
  const relY = mouseY - canvasRect.top - padding;
  
  // 칸 너비 계산
  const totalGap = gap * (columns - 1);
  const colWidth = (canvasRect.width - padding * 2 - totalGap) / columns;
  
  // 그리드 좌표 계산
  const col = Math.max(1, Math.min(columns, Math.floor(relX / (colWidth + gap)) + 1));
  const row = Math.max(1, Math.floor(relY / (rowHeight + gap)) + 1);
  
  return { col, row };
}

산출물

  • frontend/components/pop/designer/PopCanvasV5.tsx
  • frontend/components/pop/designer/panels/ComponentEditorPanelV5.tsx

Phase 5.4: 반응형 자동화

목표

모드 전환 시 자동 레이아웃 조정

작업 항목

  • 12칸 → 4칸 자동 변환 알고리즘
  • 겹침 감지 및 자동 재배치
  • 모드별 오버라이드 저장
  • "자동 배치" vs "수동 고정" 선택
  • 변환 미리보기

자동 변환 알고리즘

// 12칸 → 4칸 변환 전략
function convertLayoutToMode(
  components: PopComponentDefinitionV5[],
  sourceMode: 'tablet_landscape',  // 12칸
  targetMode: 'mobile_portrait'    // 4칸
): PopComponentDefinitionV5[] {
  const sourceColumns = 12;
  const targetColumns = 4;
  const ratio = targetColumns / sourceColumns;  // 0.333
  
  // 1. 각 컴포넌트 위치 변환
  const converted = components.map(comp => {
    const newCol = Math.max(1, Math.ceil(comp.position.col * ratio));
    const newColSpan = Math.max(1, Math.round(comp.position.colSpan * ratio));
    
    return {
      ...comp,
      position: {
        ...comp.position,
        col: newCol,
        colSpan: Math.min(newColSpan, targetColumns),
      },
    };
  });
  
  // 2. 겹침 감지 및 해결
  return resolveOverlaps(converted, targetColumns);
}

// 겹침 해결 (아래로 밀기)
function resolveOverlaps(
  components: PopComponentDefinitionV5[],
  columns: number
): PopComponentDefinitionV5[] {
  // 행 단위로 그리드 점유 상태 추적
  const grid: boolean[][] = [];
  
  // row 순서대로 처리
  const sorted = [...components].sort((a, b) => 
    a.position.row - b.position.row || a.position.col - b.position.col
  );
  
  return sorted.map(comp => {
    let { row, col, colSpan, rowSpan } = comp.position;
    
    // 배치 가능한 위치 찾기
    while (isOccupied(grid, row, col, colSpan, rowSpan, columns)) {
      row++;  // 아래로 이동
    }
    
    // 그리드에 표시
    markOccupied(grid, row, col, colSpan, rowSpan);
    
    return {
      ...comp,
      position: { row, col, colSpan, rowSpan },
    };
  });
}

산출물

  • frontend/components/pop/designer/utils/gridLayoutUtils.ts

마이그레이션 전략

v4 → v5 변환

function migrateV4ToV5(layoutV4: PopLayoutDataV4): PopLayoutDataV5 {
  const componentsList = Object.values(layoutV4.components);
  
  // Flexbox 순서 → Grid 위치 변환
  let currentRow = 1;
  let currentCol = 1;
  const columns = 12;
  
  const componentsV5: Record<string, PopComponentDefinitionV5> = {};
  
  componentsList.forEach((comp, index) => {
    // 기본 크기 추정 (픽셀 → 칸)
    const colSpan = Math.max(1, Math.round((comp.size.fixedWidth || 100) / 85));
    const rowSpan = Math.max(1, Math.round((comp.size.fixedHeight || 48) / 48));
    
    // 줄바꿈 체크
    if (currentCol + colSpan - 1 > columns) {
      currentRow++;
      currentCol = 1;
    }
    
    componentsV5[comp.id] = {
      ...comp,
      position: {
        col: currentCol,
        row: currentRow,
        colSpan,
        rowSpan,
      },
    };
    
    currentCol += colSpan;
  });
  
  return {
    version: "pop-5.0",
    gridConfig: { /* 기본값 */ },
    components: componentsV5,
    dataFlow: layoutV4.dataFlow,
    settings: { /* 변환 */ },
  };
}

하위 호환

버전 처리 방식
v1~v2 v3로 변환 후 v5로
v3 v5로 직접 변환
v4 v5로 직접 변환
v5 그대로 사용

일정 (예상)

Phase 작업 예상 기간
5.1 타입 정의 2~3일
5.2 그리드 렌더러 3~5일
5.3 디자이너 UI 5~7일
5.4 반응형 자동화 3~5일
- 테스트 및 버그 수정 2~3일
약 2~3주

리스크 및 대응

리스크 영향 대응
기존 v4 화면 깨짐 높음 하위 호환 유지, v4 렌더러 보존
자동 변환 품질 중간 수동 오버라이드로 보완
드래그 UX 복잡 중간 스냅 기능으로 편의성 확보
성능 저하 낮음 CSS Grid는 네이티브 성능

성공 기준

  1. 배치 예측 가능: "2열 3행"이라고 하면 정확히 그 위치에 표시
  2. 일관된 디자인: 12칸 → 4칸 전환 시 비율 유지
  3. 쉬운 편집: 드래그로 칸에 스냅되어 배치
  4. 하위 호환: 기존 v4 화면이 정상 동작

관련 문서


최종 업데이트: 2026-02-05