ERP-node/popdocs/archive/POPREADME.md

659 lines
23 KiB
Markdown
Raw Normal View History

# POP 화면 시스템 구현 계획서
## 개요
Vexplor 서비스 내에서 POP(Point of Production) 화면을 구성할 수 있는 시스템을 구현합니다.
기존 Vexplor와 충돌 없이 별도 공간에서 개발하되, 장기적으로 통합 가능하도록 동일한 서비스 로직을 사용합니다.
---
## 핵심 원칙
| 원칙 | 설명 |
|------|------|
| **충돌 방지** | POP 전용 공간에서 개발 |
| **통합 준비** | 기본 서비스 로직은 Vexplor와 동일 |
| **데이터 공유** | 같은 DB, 같은 데이터 소스 사용 |
---
## 아키텍처 개요
```
┌─────────────────────────────────────────────────────────────────────┐
│ [데이터베이스] │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ screen_ │ │ screen_layouts_ │ │ screen_layouts_ │ │
│ │ definitions │ │ v2 (데스크톱) │ │ pop (POP) │ │
│ │ (공통) │ └─────────────────┘ └─────────────────┘ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ [백엔드 API] │
│ /screen-management/screens/:id/layout-v2 (데스크톱) │
│ /screen-management/screens/:id/layout-pop (POP) │
└─────────────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ [프론트엔드 - 데스크톱] │ │ [프론트엔드 - POP] │
│ │ │ │
│ app/(main)/ │ │ app/(pop)/ │
│ lib/registry/ │ │ lib/registry/ │
│ components/ │ │ pop-components/ │
│ components/screen/ │ │ components/pop/ │
└─────────────────────────┘ └─────────────────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ PC 브라우저 │ │ 모바일/태블릿 브라우저 │
│ (마우스 + 키보드) │ │ (터치 + 스캐너) │
└─────────────────────────┘ └─────────────────────────┘
```
---
## 1. 데이터베이스 변경사항
### 1-1. 테이블 추가/유지 현황
| 구분 | 테이블명 | 변경 내용 | 비고 |
|------|----------|----------|------|
| **추가** | `screen_layouts_pop` | POP 레이아웃 저장용 | 신규 테이블 |
| **유지** | `screen_definitions` | 변경 없음 | 공통 사용 |
| **유지** | `screen_layouts_v2` | 변경 없음 | 데스크톱 전용 |
### 1-2. 신규 테이블 DDL
```sql
-- 마이그레이션 파일: db/migrations/XXX_create_screen_layouts_pop.sql
CREATE TABLE screen_layouts_pop (
layout_id SERIAL PRIMARY KEY,
screen_id INTEGER NOT NULL REFERENCES screen_definitions(screen_id),
company_code VARCHAR(20) NOT NULL,
layout_data JSONB NOT NULL DEFAULT '{}'::jsonb, -- 반응형 레이아웃 JSON
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by VARCHAR(50),
updated_by VARCHAR(50),
UNIQUE(screen_id, company_code)
);
CREATE INDEX idx_pop_screen_id ON screen_layouts_pop(screen_id);
CREATE INDEX idx_pop_company_code ON screen_layouts_pop(company_code);
COMMENT ON TABLE screen_layouts_pop IS 'POP 화면 레이아웃 저장 테이블 (모바일/태블릿 반응형)';
COMMENT ON COLUMN screen_layouts_pop.layout_data IS 'V2 형식의 레이아웃 JSON (반응형 구조)';
```
### 1-3. 레이아웃 JSON 구조 (V2 형식 동일)
```json
{
"version": "2.0",
"components": [
{
"id": "comp_xxx",
"url": "@/lib/registry/pop-components/pop-card-list",
"position": { "x": 0, "y": 0 },
"size": { "width": 100, "height": 50 },
"displayOrder": 0,
"overrides": {
"tableName": "user_info",
"columns": ["id", "name", "status"],
"cardStyle": "compact"
}
}
],
"updatedAt": "2026-01-29T12:00:00Z"
}
```
---
## 2. 백엔드 변경사항
### 2-1. 파일 수정 목록
| 구분 | 파일 경로 | 변경 내용 |
|------|----------|----------|
| **수정** | `backend-node/src/services/screenManagementService.ts` | POP 레이아웃 CRUD 함수 추가 |
| **수정** | `backend-node/src/routes/screenManagementRoutes.ts` | POP API 엔드포인트 추가 |
### 2-2. 추가 API 엔드포인트
```
GET /screen-management/screens/:screenId/layout-pop # POP 레이아웃 조회
POST /screen-management/screens/:screenId/layout-pop # POP 레이아웃 저장
DELETE /screen-management/screens/:screenId/layout-pop # POP 레이아웃 삭제
```
### 2-3. screenManagementService.ts 추가 함수
```typescript
// 기존 함수 (유지)
getScreenLayoutV2(screenId, companyCode)
saveLayoutV2(screenId, companyCode, layoutData)
// 추가 함수 (신규) - 로직은 V2와 동일, 테이블명만 다름
getScreenLayoutPop(screenId, companyCode)
saveLayoutPop(screenId, companyCode, layoutData)
deleteLayoutPop(screenId, companyCode)
```
---
## 3. 프론트엔드 변경사항
### 3-1. 폴더 구조
```
frontend/
├── app/
│ └── (pop)/ # [기존] POP 라우팅 그룹
│ ├── layout.tsx # [수정] POP 전용 레이아웃
│ ├── pop/
│ │ └── page.tsx # [기존] POP 메인
│ └── screens/ # [추가] POP 화면 뷰어
│ └── [screenId]/
│ └── page.tsx # [추가] POP 동적 화면
├── lib/
│ ├── api/
│ │ └── screen.ts # [수정] POP API 함수 추가
│ │
│ ├── registry/
│ │ ├── pop-components/ # [추가] POP 전용 컴포넌트
│ │ │ ├── pop-card-list/
│ │ │ │ ├── PopCardListComponent.tsx
│ │ │ │ ├── PopCardListConfigPanel.tsx
│ │ │ │ └── index.ts
│ │ │ ├── pop-touch-button/
│ │ │ ├── pop-scanner-input/
│ │ │ └── index.ts # POP 컴포넌트 내보내기
│ │ │
│ │ ├── PopComponentRegistry.ts # [추가] POP 컴포넌트 레지스트리
│ │ └── ComponentRegistry.ts # [유지] 기존 유지
│ │
│ ├── schemas/
│ │ └── popComponentConfig.ts # [추가] POP용 Zod 스키마
│ │
│ └── utils/
│ └── layoutPopConverter.ts # [추가] POP 레이아웃 변환기
└── components/
└── pop/ # [기존] POP UI 컴포넌트
├── PopScreenDesigner.tsx # [추가] POP 화면 설계 도구
├── PopPreview.tsx # [추가] POP 미리보기
└── PopDynamicRenderer.tsx # [추가] POP 동적 렌더러
```
### 3-2. 파일별 상세 내용
#### A. 신규 파일 (추가)
| 파일 | 역할 | 기반 |
|------|------|------|
| `app/(pop)/screens/[screenId]/page.tsx` | POP 화면 뷰어 | `app/(main)/screens/[screenId]/page.tsx` 참고 |
| `lib/registry/PopComponentRegistry.ts` | POP 컴포넌트 등록 | `ComponentRegistry.ts` 구조 동일 |
| `lib/registry/pop-components/*` | POP 전용 컴포넌트 | 신규 개발 |
| `lib/schemas/popComponentConfig.ts` | POP Zod 스키마 | `componentConfig.ts` 구조 동일 |
| `lib/utils/layoutPopConverter.ts` | POP 레이아웃 변환 | `layoutV2Converter.ts` 구조 동일 |
| `components/pop/PopScreenDesigner.tsx` | POP 화면 설계 | 신규 개발 |
| `components/pop/PopDynamicRenderer.tsx` | POP 동적 렌더러 | `DynamicComponentRenderer.tsx` 참고 |
#### B. 수정 파일
| 파일 | 변경 내용 |
|------|----------|
| `lib/api/screen.ts` | `getLayoutPop()`, `saveLayoutPop()` 함수 추가 |
| `app/(pop)/layout.tsx` | POP 전용 레이아웃 스타일 적용 |
#### C. 유지 파일 (변경 없음)
| 파일 | 이유 |
|------|------|
| `lib/registry/ComponentRegistry.ts` | 데스크톱 전용, 분리 유지 |
| `lib/schemas/componentConfig.ts` | 데스크톱 전용, 분리 유지 |
| `lib/utils/layoutV2Converter.ts` | 데스크톱 전용, 분리 유지 |
| `app/(main)/*` | 데스크톱 전용, 변경 없음 |
---
## 4. 서비스 로직 흐름
### 4-1. 데스크톱 (기존 - 변경 없음)
```
[사용자] → /screens/123 접속
[app/(main)/screens/[screenId]/page.tsx]
[getLayoutV2(screenId)] → API 호출
[screen_layouts_v2 테이블] → 레이아웃 JSON 반환
[DynamicComponentRenderer] → 컴포넌트 렌더링
[ComponentRegistry] → 컴포넌트 찾기
[lib/registry/components/table-list] → 컴포넌트 실행
[화면 표시]
```
### 4-2. POP (신규 - 동일 로직)
```
[사용자] → /pop/screens/123 접속
[app/(pop)/screens/[screenId]/page.tsx]
[getLayoutPop(screenId)] → API 호출
[screen_layouts_pop 테이블] → 레이아웃 JSON 반환
[PopDynamicRenderer] → 컴포넌트 렌더링
[PopComponentRegistry] → 컴포넌트 찾기
[lib/registry/pop-components/pop-card-list] → 컴포넌트 실행
[화면 표시]
```
---
## 5. 로직 변경 여부
| 구분 | 로직 변경 | 설명 |
|------|----------|------|
| 데이터베이스 CRUD | **없음** | 동일한 SELECT/INSERT/UPDATE 패턴 |
| API 호출 방식 | **없음** | 동일한 REST API 패턴 |
| 컴포넌트 렌더링 | **없음** | 동일한 URL 기반 + overrides 방식 |
| Zod 스키마 검증 | **없음** | 동일한 검증 로직 |
| 레이아웃 JSON 구조 | **없음** | 동일한 V2 JSON 구조 사용 |
**결론: 로직 변경 없음, 파일/테이블 분리만 진행**
---
## 6. 데스크톱 vs POP 비교
| 구분 | Vexplor (데스크톱) | POP (모바일/태블릿) |
|------|-------------------|---------------------|
| **타겟 기기** | PC (마우스+키보드) | 모바일/태블릿 (터치) |
| **화면 크기** | 1920x1080 고정 | 반응형 (다양한 크기) |
| **UI 스타일** | 테이블 중심, 작은 버튼 | 카드 중심, 큰 터치 버튼 |
| **입력 방식** | 키보드 타이핑 | 터치, 스캐너, 음성 |
| **사용 환경** | 사무실 | 현장, 창고, 공장 |
| **레이아웃 테이블** | `screen_layouts_v2` | `screen_layouts_pop` |
| **컴포넌트 경로** | `lib/registry/components/` | `lib/registry/pop-components/` |
| **레지스트리** | `ComponentRegistry.ts` | `PopComponentRegistry.ts` |
---
## 7. 장기 통합 시나리오
### Phase 1: 분리 개발 (현재 목표)
```
[데스크톱] [POP]
ComponentRegistry PopComponentRegistry
components/ pop-components/
screen_layouts_v2 screen_layouts_pop
```
### Phase 2: 부분 통합 (향후)
```
[통합 가능한 부분]
- 공통 유틸리티 함수
- 공통 Zod 스키마
- 공통 타입 정의
[분리 유지]
- 플랫폼별 컴포넌트
- 플랫폼별 레이아웃
```
### Phase 3: 완전 통합 (최종)
```
[단일 컴포넌트 레지스트리]
ComponentRegistry
├── components/ (공통)
├── desktop-components/ (데스크톱 전용)
└── pop-components/ (POP 전용)
[단일 레이아웃 테이블] (선택사항)
screen_layouts
├── platform = 'desktop'
└── platform = 'pop'
```
---
## 8. V2 공통 요소 (통합 핵심)
POP과 데스크톱이 장기적으로 통합될 수 있는 **핵심 기반**입니다.
### 8-1. 공통 유틸리티 함수
**파일 위치:** `frontend/lib/schemas/componentConfig.ts`, `frontend/lib/utils/layoutV2Converter.ts`
#### 핵심 병합/추출 함수 (가장 중요!)
| 함수명 | 역할 | 사용 시점 |
|--------|------|----------|
| `deepMerge()` | 객체 깊은 병합 | 기본값 + overrides 합칠 때 |
| `mergeComponentConfig()` | 기본값 + 커스텀 병합 | **렌더링 시** (화면 표시) |
| `extractCustomConfig()` | 기본값과 다른 부분만 추출 | **저장 시** (DB 저장) |
| `isDeepEqual()` | 두 객체 깊은 비교 | 변경 여부 판단 |
```typescript
// 예시: 저장 시 차이값만 추출
const defaults = { showHeader: true, pageSize: 20 };
const fullConfig = { showHeader: true, pageSize: 50, customField: "test" };
const overrides = extractCustomConfig(fullConfig, defaults);
// 결과: { pageSize: 50, customField: "test" } (차이값만!)
```
#### URL 처리 함수
| 함수명 | 역할 | 예시 |
|--------|------|------|
| `getComponentUrl()` | 타입 → URL 변환 | `"v2-table-list"``"@/lib/registry/components/v2-table-list"` |
| `getComponentTypeFromUrl()` | URL → 타입 추출 | `"@/lib/registry/components/v2-table-list"``"v2-table-list"` |
#### 기본값 조회 함수
| 함수명 | 역할 |
|--------|------|
| `getComponentDefaults()` | 컴포넌트 타입으로 기본값 조회 |
| `getDefaultsByUrl()` | URL로 기본값 조회 |
#### V2 로드/저장 함수 (핵심!)
| 함수명 | 역할 | 사용 시점 |
|--------|------|----------|
| `loadComponentV2()` | 컴포넌트 로드 (기본값 병합) | DB → 화면 |
| `saveComponentV2()` | 컴포넌트 저장 (차이값 추출) | 화면 → DB |
| `loadLayoutV2()` | 레이아웃 전체 로드 | DB → 화면 |
| `saveLayoutV2()` | 레이아웃 전체 저장 | 화면 → DB |
#### 변환 함수
| 함수명 | 역할 |
|--------|------|
| `convertV2ToLegacy()` | V2 → Legacy 변환 (하위 호환) |
| `convertLegacyToV2()` | Legacy → V2 변환 |
| `isValidV2Layout()` | V2 레이아웃인지 검증 |
| `isLegacyLayout()` | 레거시 레이아웃인지 확인 |
### 8-2. 공통 Zod 스키마
**파일 위치:** `frontend/lib/schemas/componentConfig.ts`
#### 핵심 스키마 (필수!)
```typescript
// 컴포넌트 기본 구조
export const componentV2Schema = z.object({
id: z.string(),
url: z.string(),
position: z.object({ x: z.number(), y: z.number() }),
size: z.object({ width: z.number(), height: z.number() }),
displayOrder: z.number().default(0),
overrides: z.record(z.string(), z.any()).default({}),
});
// 레이아웃 기본 구조
export const layoutV2Schema = z.object({
version: z.string().default("2.0"),
components: z.array(componentV2Schema).default([]),
updatedAt: z.string().optional(),
screenResolution: z.object({...}).optional(),
gridSettings: z.any().optional(),
});
```
#### 컴포넌트별 overrides 스키마 (25개+)
| 스키마명 | 컴포넌트 | 주요 기본값 |
|----------|----------|------------|
| `v2TableListOverridesSchema` | 테이블 리스트 | displayMode: "table", pageSize: 20 |
| `v2ButtonPrimaryOverridesSchema` | 버튼 | text: "저장", variant: "primary" |
| `v2SplitPanelLayoutOverridesSchema` | 분할 레이아웃 | splitRatio: 30, resizable: true |
| `v2SectionCardOverridesSchema` | 섹션 카드 | padding: "md", collapsible: false |
| `v2TabsWidgetOverridesSchema` | 탭 위젯 | orientation: "horizontal" |
| `v2RepeaterOverridesSchema` | 리피터 | renderMode: "inline" |
#### 스키마 레지스트리 (자동 매핑)
```typescript
const componentOverridesSchemaRegistry = {
"v2-table-list": v2TableListOverridesSchema,
"v2-button-primary": v2ButtonPrimaryOverridesSchema,
"v2-split-panel-layout": v2SplitPanelLayoutOverridesSchema,
// ... 25개+ 컴포넌트
};
```
### 8-3. 공통 타입 정의
**파일 위치:** `frontend/types/v2-core.ts`, `frontend/types/v2-components.ts`
#### 핵심 공통 타입 (v2-core.ts)
```typescript
// 웹 입력 타입
export type WebType =
| "text" | "textarea" | "email" | "tel" | "url"
| "number" | "decimal"
| "date" | "datetime"
| "select" | "dropdown" | "radio" | "checkbox" | "boolean"
| "code" | "entity" | "file" | "image" | "button"
| "container" | "group" | "list" | "tree" | "custom";
// 버튼 액션 타입
export type ButtonActionType =
| "save" | "cancel" | "delete" | "edit" | "copy" | "add"
| "search" | "reset" | "submit"
| "close" | "popup" | "modal"
| "navigate" | "newWindow"
| "control" | "transferData" | "quickInsert";
// 위치/크기
export interface Position { x: number; y: number; z?: number; }
export interface Size { width: number; height: number; }
// 공통 스타일
export interface CommonStyle {
margin?: string;
padding?: string;
border?: string;
backgroundColor?: string;
color?: string;
fontSize?: string;
// ... 30개+ 속성
}
// 유효성 검사
export interface ValidationRule {
type: "required" | "minLength" | "maxLength" | "pattern" | "min" | "max" | "email" | "url";
value?: unknown;
message: string;
}
```
#### V2 컴포넌트 타입 (v2-components.ts)
```typescript
// 10개 통합 컴포넌트 타입
export type V2ComponentType =
| "V2Input" | "V2Select" | "V2Date" | "V2Text" | "V2Media"
| "V2List" | "V2Layout" | "V2Group" | "V2Biz" | "V2Hierarchy";
// 공통 속성
export interface V2BaseProps {
id: string;
label?: string;
required?: boolean;
readonly?: boolean;
disabled?: boolean;
tableName?: string;
columnName?: string;
position?: Position;
size?: Size;
style?: CommonStyle;
validation?: ValidationRule[];
}
```
### 8-4. POP 통합 시 공유/분리 기준
#### 반드시 공유 (그대로 사용)
| 구분 | 파일/요소 | 이유 |
|------|----------|------|
| **유틸리티** | `deepMerge`, `extractCustomConfig`, `mergeComponentConfig` | 저장/로드 로직 동일 |
| **스키마** | `componentV2Schema`, `layoutV2Schema` | JSON 구조 동일 |
| **타입** | `Position`, `Size`, `WebType`, `ButtonActionType` | 기본 구조 동일 |
#### POP 전용으로 분리
| 구분 | 파일/요소 | 이유 |
|------|----------|------|
| **overrides 스키마** | `popCardListOverridesSchema` 등 | POP 컴포넌트 전용 기본값 |
| **스키마 레지스트리** | `popComponentOverridesSchemaRegistry` | POP 컴포넌트 매핑 |
| **기본값 레지스트리** | `popComponentDefaultsRegistry` | POP 컴포넌트 기본값 |
### 8-5. 추천 폴더 구조 (공유 분리)
```
frontend/lib/schemas/
├── componentConfig.ts # 기존 (데스크톱)
├── popComponentConfig.ts # 신규 (POP) - 구조는 동일
└── shared/ # 신규 (공유) - 향후 통합 시
├── baseSchemas.ts # componentV2Schema, layoutV2Schema
├── mergeUtils.ts # deepMerge, extractCustomConfig 등
└── types.ts # Position, Size 등
```
---
## 9. 작업 우선순위
### [ ] 1단계: 데이터베이스
- [ ] `screen_layouts_pop` 테이블 생성 마이그레이션 작성
- [ ] 마이그레이션 실행 및 검증
### [ ] 2단계: 백엔드 API
- [ ] `screenManagementService.ts`에 POP 함수 추가
- [ ] `getScreenLayoutPop()`
- [ ] `saveLayoutPop()`
- [ ] `deleteLayoutPop()`
- [ ] `screenManagementRoutes.ts`에 엔드포인트 추가
- [ ] `GET /screens/:screenId/layout-pop`
- [ ] `POST /screens/:screenId/layout-pop`
- [ ] `DELETE /screens/:screenId/layout-pop`
### [ ] 3단계: 프론트엔드 기반
- [ ] `lib/api/screen.ts`에 POP API 함수 추가
- [ ] `getLayoutPop()`
- [ ] `saveLayoutPop()`
- [ ] `lib/registry/PopComponentRegistry.ts` 생성
- [ ] `lib/schemas/popComponentConfig.ts` 생성
- [ ] `lib/utils/layoutPopConverter.ts` 생성
### [ ] 4단계: POP 컴포넌트 개발
- [ ] `lib/registry/pop-components/` 폴더 구조 생성
- [ ] 기본 컴포넌트 개발
- [ ] `pop-card-list` (카드형 리스트)
- [ ] `pop-touch-button` (터치 버튼)
- [ ] `pop-scanner-input` (스캐너 입력)
- [ ] `pop-status-badge` (상태 배지)
### [ ] 5단계: POP 화면 페이지
- [ ] `app/(pop)/screens/[screenId]/page.tsx` 생성
- [ ] `components/pop/PopDynamicRenderer.tsx` 생성
- [ ] `app/(pop)/layout.tsx` 수정 (POP 전용 스타일)
### [ ] 6단계: POP 화면 디자이너 (선택)
- [ ] `components/pop/PopScreenDesigner.tsx` 생성
- [ ] `components/pop/PopPreview.tsx` 생성
- [ ] 관리자 메뉴에 POP 화면 설계 기능 추가
---
## 10. 참고 파일 위치
### 데스크톱 참고 파일 (기존)
| 구분 | 파일 경로 |
|------|----------|
| 화면 페이지 | `frontend/app/(main)/screens/[screenId]/page.tsx` |
| 컴포넌트 레지스트리 | `frontend/lib/registry/ComponentRegistry.ts` |
| 동적 렌더러 | `frontend/lib/registry/DynamicComponentRenderer.tsx` |
| Zod 스키마 | `frontend/lib/schemas/componentConfig.ts` |
| 레이아웃 변환기 | `frontend/lib/utils/layoutV2Converter.ts` |
| 화면 API | `frontend/lib/api/screen.ts` |
| 백엔드 서비스 | `backend-node/src/services/screenManagementService.ts` |
| 백엔드 라우트 | `backend-node/src/routes/screenManagementRoutes.ts` |
### 관련 문서
| 문서 | 경로 |
|------|------|
| V2 아키텍처 | `docs/DDD1542/COMPONENT_LAYOUT_V2_ARCHITECTURE.md` |
| 화면관리 설계 | `docs/kjs/화면관리_시스템_설계.md` |
---
## 11. 주의사항
### 멀티테넌시
- 모든 테이블에 `company_code` 필수
- 모든 쿼리에 `company_code` 필터링 적용
- 최고 관리자(`company_code = "*"`)는 모든 데이터 조회 가능
### 충돌 방지
- 기존 데스크톱 파일 수정 최소화
- POP 전용 폴더/파일에서 작업
- 공통 로직은 별도 유틸리티로 분리
### 테스트
- 데스크톱 기능 회귀 테스트 필수
- POP 반응형 테스트 (모바일/태블릿)
- 멀티테넌시 격리 테스트
---
## 변경 이력
| 날짜 | 버전 | 내용 |
|------|------|------|
| 2026-01-29 | 1.0 | 초기 계획서 작성 |
| 2026-01-29 | 1.1 | V2 공통 요소 (통합 핵심) 섹션 추가 |
---
## 작성자
- 작성일: 2026-01-29
- 프로젝트: Vexplor POP 화면 시스템