# POP 화면 시스템 아키텍처 **최종 업데이트: 2026-02-06 (v5.2 브레이크포인트 재설계 + 세로 자동 확장)** POP(Point of Production) 화면은 모바일/태블릿 환경에 최적화된 터치 기반 화면 시스템입니다. --- ## 현재 버전: v5 (CSS Grid) | 항목 | v5 (현재) | |------|----------| | 레이아웃 | CSS Grid | | 배치 방식 | 좌표 기반 (col, row, colSpan, rowSpan) | | 모드 | 4개 (mobile_portrait, mobile_landscape, tablet_portrait, tablet_landscape) | | 칸 수 | 4/6/8/12칸 | --- ## 폴더 구조 ``` frontend/ ├── app/(pop)/ # Next.js App Router │ ├── layout.tsx # POP 전용 레이아웃 │ └── pop/ │ ├── page.tsx # 대시보드 │ ├── screens/[screenId]/ # 화면 뷰어 (v5) │ └── work/ # 작업 화면 │ ├── components/pop/ # POP 컴포넌트 │ ├── designer/ # 디자이너 모듈 ★ │ │ ├── PopDesigner.tsx # 메인 (레이아웃 로드/저장) │ │ ├── PopCanvas.tsx # 캔버스 (DnD, 줌, 모드) │ │ ├── panels/ │ │ │ └── ComponentEditorPanel.tsx # 속성 편집 │ │ ├── renderers/ │ │ │ └── PopRenderer.tsx # CSS Grid 렌더링 │ │ ├── types/ │ │ │ └── pop-layout.ts # v5 타입 정의 │ │ └── utils/ │ │ └── gridUtils.ts # 위치 계산 │ ├── management/ # 화면 관리 │ └── dashboard/ # 대시보드 │ └── lib/ ├── api/screen.ts # 화면 API └── registry/ # 컴포넌트 레지스트리 ``` --- ## 핵심 파일 ### 1. PopDesigner.tsx (메인) **역할**: 레이아웃 로드/저장, 컴포넌트 CRUD, 히스토리 ```typescript // 상태 관리 const [layout, setLayout] = useState(createEmptyPopLayoutV5()); const [selectedComponentId, setSelectedComponentId] = useState(null); const [currentMode, setCurrentMode] = useState("tablet_landscape"); const [history, setHistory] = useState([]); // 핵심 함수 handleSave() // 레이아웃 저장 handleAddComponent() // 컴포넌트 추가 handleUpdateComponent() // 컴포넌트 수정 handleDeleteComponent() // 컴포넌트 삭제 handleUndo() / handleRedo() // 히스토리 ``` ### 2. PopCanvas.tsx (캔버스) **역할**: 그리드 캔버스, DnD, 줌, 패닝, 모드 전환 ```typescript // DnD 설정 const DND_ITEM_TYPES = { COMPONENT: "component" }; // 뷰포트 프리셋 (4개 모드) - height 제거됨 (세로 무한 스크롤) const VIEWPORT_PRESETS = [ { id: "mobile_portrait", width: 375, columns: 4 }, { id: "mobile_landscape", width: 600, columns: 6 }, { id: "tablet_portrait", width: 834, columns: 8 }, { id: "tablet_landscape", width: 1024, columns: 12 }, ]; // 세로 자동 확장 const MIN_CANVAS_HEIGHT = 600; // 최소 캔버스 높이 const CANVAS_EXTRA_ROWS = 3; // 항상 유지되는 여유 행 수 const dynamicCanvasHeight = useMemo(() => { ... }, []); // 기능 - useDrop(): 팔레트에서 컴포넌트 드롭 - handleWheel(): 줌 (30%~150%) - Space + 드래그: 패닝 ``` ### 3. PopRenderer.tsx (렌더러) **역할**: CSS Grid 기반 레이아웃 렌더링 ```typescript // Props interface PopRendererProps { layout: PopLayoutDataV5; viewportWidth: number; currentMode: GridMode; isDesignMode: boolean; selectedComponentId?: string | null; onSelectComponent?: (id: string | null) => void; } // CSS Grid 스타일 생성 const gridStyle = useMemo(() => ({ display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gridTemplateRows: `repeat(${rows}, 1fr)`, gap: `${gap}px`, padding: `${padding}px`, }), [mode]); // 위치 변환 (12칸 → 다른 모드) const convertPosition = (pos: PopGridPosition, targetMode: GridMode) => { const ratio = GRID_BREAKPOINTS[targetMode].columns / 12; return { col: Math.max(1, Math.round(pos.col * ratio)), colSpan: Math.max(1, Math.round(pos.colSpan * ratio)), row: pos.row, rowSpan: pos.rowSpan, }; }; ``` ### 4. ComponentEditorPanel.tsx (속성 패널) **역할**: 선택된 컴포넌트 속성 편집 ```typescript // 탭 구조 - grid: col, row, colSpan, rowSpan (기본 모드에서만 편집) - settings: label, type 등 - data: 데이터 바인딩 (미구현) - visibility: 모드별 표시/숨김 ``` ### 5. pop-layout.ts (타입 정의) **역할**: v5 타입 정의 ```typescript // 그리드 모드 type GridMode = "mobile_portrait" | "mobile_landscape" | "tablet_portrait" | "tablet_landscape"; // 브레이크포인트 설정 (2026-02-06 재설계) const GRID_BREAKPOINTS = { mobile_portrait: { columns: 4, maxWidth: 479, gap: 8, padding: 12 }, mobile_landscape: { columns: 6, minWidth: 480, maxWidth: 767, gap: 8, padding: 16 }, tablet_portrait: { columns: 8, minWidth: 768, maxWidth: 1023, gap: 12, padding: 20 }, tablet_landscape: { columns: 12, minWidth: 1024, gap: 12, padding: 24 }, }; // 모드 감지 (순수 너비 기반) function detectGridMode(viewportWidth: number): GridMode { if (viewportWidth < 480) return "mobile_portrait"; if (viewportWidth < 768) return "mobile_landscape"; if (viewportWidth < 1024) return "tablet_portrait"; return "tablet_landscape"; } // 레이아웃 데이터 interface PopLayoutDataV5 { version: "pop-5.0"; metadata: PopLayoutMetadata; gridConfig: PopGridConfig; components: PopComponentDefinitionV5[]; globalSettings: PopGlobalSettingsV5; } // 컴포넌트 정의 interface PopComponentDefinitionV5 { id: string; type: PopComponentType; label: string; gridPosition: PopGridPosition; // col, row, colSpan, rowSpan config: PopComponentConfig; visibility: Record; modeOverrides?: Record; } // 위치 interface PopGridPosition { col: number; // 시작 열 (1부터) row: number; // 시작 행 (1부터) colSpan: number; // 열 크기 (1~12) rowSpan: number; // 행 크기 (1~) } ``` ### 6. gridUtils.ts (유틸리티) **역할**: 그리드 위치 계산 ```typescript // 위치 변환 convertPositionToMode(pos, targetMode) // 겹침 감지 isOverlapping(posA, posB) // 빈 위치 찾기 findNextEmptyPosition(layout, mode) // 마우스 → 그리드 좌표 mouseToGridPosition(mouseX, mouseY, canvasRect, mode) ``` --- ## 데이터 흐름 ``` [사용자 액션] ↓ [PopDesigner] ← 상태 관리 (layout, selectedComponentId, history) ↓ [PopCanvas] ← DnD, 줌, 모드 전환 ↓ [PopRenderer] ← CSS Grid 렌더링 ↓ [컴포넌트 표시] ``` ### 저장 흐름 ``` [저장 버튼] ↓ PopDesigner.handleSave() ↓ screenApi.saveLayoutPop(screenId, layout) ↓ [백엔드] screenManagementService.saveLayoutPop() ↓ [DB] screen_layouts_pop 테이블 ``` ### 로드 흐름 ``` [페이지 로드] ↓ PopDesigner useEffect ↓ screenApi.getLayoutPop(screenId) ↓ isV5Layout(data) 체크 ↓ setLayout(data) 또는 createEmptyPopLayoutV5() ``` --- ## API 엔드포인트 | 메서드 | 경로 | 설명 | |--------|------|------| | GET | `/api/screen-management/layout-pop/:screenId` | 레이아웃 조회 | | POST | `/api/screen-management/layout-pop/:screenId` | 레이아웃 저장 | --- ## 삭제된 레거시 (참고용) | 파일 | 버전 | 이유 | |------|------|------| | PopCanvasV4.tsx | v4 | Flexbox 기반, v5로 대체 | | PopFlexRenderer.tsx | v4 | Flexbox 렌더러, v5로 대체 | | PopLayoutRenderer.tsx | v3 | 절대 좌표 기반, v5로 대체 | | ComponentEditorPanelV4.tsx | v4 | v5 전용으로 통합 | --- *상세 스펙: [SPEC.md](./SPEC.md) | 파일 목록: [FILES.md](./FILES.md)*