ERP-node/POPREADME.md

23 KiB

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

-- 마이그레이션 파일: 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 형식 동일)

{
  "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 추가 함수

// 기존 함수 (유지)
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() 두 객체 깊은 비교 변경 여부 판단
// 예시: 저장 시 차이값만 추출
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

핵심 스키마 (필수!)

// 컴포넌트 기본 구조
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"

스키마 레지스트리 (자동 매핑)

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)

// 웹 입력 타입
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)

// 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 화면 시스템