ERP-node/popdocs/ARCHITECTURE.md

287 lines
7.8 KiB
Markdown

# 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<PopLayoutDataV5>(createEmptyPopLayoutV5());
const [selectedComponentId, setSelectedComponentId] = useState<string | null>(null);
const [currentMode, setCurrentMode] = useState<GridMode>("tablet_landscape");
const [history, setHistory] = useState<PopLayoutDataV5[]>([]);
// 핵심 함수
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<GridMode, boolean>;
modeOverrides?: Record<GridMode, PopModeOverrideV5>;
}
// 위치
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)*