ERP-node/POPUPDATE.md

22 KiB

POP 화면 관리 시스템 개발 기록

AI 에이전트 안내: 이 문서는 Progressive Disclosure 방식으로 구성되어 있습니다.

  1. 먼저 Quick Reference에서 필요한 정보 확인
  2. 상세 내용이 필요하면 해당 섹션으로 이동
  3. 코드가 필요하면 파일 직접 참조

Quick Reference

POP이란?

Point of Production - 현장 작업자용 모바일/태블릿 화면 시스템

핵심 결정사항

  • 분리 방식: 레이아웃 기반 구분 (screen_layouts_pop 테이블)
  • 식별 방법: screen_layouts_pop에 레코드 존재 여부로 POP 화면 판별
  • 데스크톱 영향: 없음 (모든 isPop 기본값 = false)

주요 경로

용도 경로
POP 뷰어 URL /pop/screens/{screenId}?preview=true&device=tablet
POP 관리 페이지 /admin/screenMng/popScreenMngList
POP 레이아웃 API /api/screen-management/layout-pop/:screenId

파일 찾기 가이드

작업 파일
POP 레이아웃 DB 스키마 db/migrations/052_create_screen_layouts_pop.sql
POP API 서비스 로직 backend-node/src/services/screenManagementService.ts (getLayoutPop, saveLayoutPop)
POP API 라우트 backend-node/src/routes/screenManagementRoutes.ts
프론트엔드 API 클라이언트 frontend/lib/api/screen.ts (screenApi.getLayoutPop 등)
POP 화면 관리 UI frontend/app/(main)/admin/screenMng/popScreenMngList/page.tsx
POP 뷰어 페이지 frontend/app/(pop)/pop/screens/[screenId]/page.tsx
미리보기 URL 분기 frontend/components/screen/ScreenSettingModal.tsx (PreviewTab)
POP 컴포넌트 설계서 docs/pop/components-spec.md (13개 컴포넌트 상세)

섹션 목차

# 섹션 한 줄 요약
1 아키텍처 레이아웃 테이블로 POP/데스크톱 분리
2 데이터베이스 screen_layouts_pop 테이블 (FK 없음)
3 백엔드 API CRUD 4개 엔드포인트
4 프론트엔드 API screenApi에 4개 함수 추가
5 관리 페이지 POP 화면만 필터링하여 표시
6 뷰어 모바일/태블릿 프레임 미리보기
7 미리보기 isPop prop으로 URL 분기
8 파일 목록 생성 3개, 수정 9개
9 반응형 전략 Flow 레이아웃 (세로 쌓기) 채택
10 POP 사용자 앱 대시보드 카드 → 화면 뷰어
11 POP 디자이너 좌(탭패널) + 우(팬캔버스), 반응형 편집
12 데이터 구조 PopLayoutData, mobileOverride
13 컴포넌트 재사용성 2개 재사용, 4개 부분, 7개 신규

1. 아키텍처

결정: Option B (레이아웃 기반 구분)

screen_definitions (공용)
    ├── screen_layouts_v2 (데스크톱)
    └── screen_layouts_pop (POP)

선택 이유: 기존 테이블 변경 없음, 데스크톱 영향 없음, 향후 통합 가능


2. 데이터베이스

테이블: screen_layouts_pop

컬럼 타입 설명
id SERIAL PK
screen_id INTEGER 화면 ID (unique)
layout_data JSONB 컴포넌트 JSON

특이사항: FK 없음 (soft-delete 지원)

파일: db/migrations/052_create_screen_layouts_pop.sql


3. 백엔드 API

Method Endpoint 용도
GET /api/screen-management/layout-pop/:screenId 조회
POST /api/screen-management/layout-pop/:screenId 저장
DELETE /api/screen-management/layout-pop/:screenId 삭제
GET /api/screen-management/pop-layout-screen-ids ID 목록

파일: backend-node/src/services/screenManagementService.ts


4. 프론트엔드 API

파일: frontend/lib/api/screen.ts

screenApi.getLayoutPop(screenId)           // 조회
screenApi.saveLayoutPop(screenId, data)    // 저장
screenApi.deleteLayoutPop(screenId)        // 삭제
screenApi.getScreenIdsWithPopLayout()      // ID 목록

5. 관리 페이지

파일: frontend/app/(main)/admin/screenMng/popScreenMngList/page.tsx

핵심 로직:

const popIds = await screenApi.getScreenIdsWithPopLayout();
const filteredScreens = screens.filter(s => new Set(popIds).has(s.screenId));

기능: POP 화면만 표시, 새 POP 화면 생성):, 보기/설계 버튼


6. 뷰어

파일: frontend/app/(pop)/pop/screens/[screenId]/page.tsx

URL 파라미터:

파라미터 설명
preview true 툴바 표시
device mobile/tablet 디바이스 크기 (기본: tablet)

디바이스 크기: mobile(375x812), tablet(768x1024)


7. 미리보기

핵심: isPop prop으로 URL 분기

popScreenMngList
    └─► ScreenRelationFlow(isPop=true)
            └─► ScreenSettingModal
                    └─► PreviewTab → /pop/screens/{id}

screenMngList (데스크톱)
    └─► ScreenRelationFlow(isPop=false 기본값)
            └─► ScreenSettingModal
                    └─► PreviewTab → /screens/{id}

안전성: isPop 기본값 = false → 데스크톱 영향 없음


8. 파일 목록

생성 (3개)

파일 용도
db/migrations/052_create_screen_layouts_pop.sql DB 스키마
frontend/app/(pop)/pop/screens/[screenId]/page.tsx POP 뷰어
frontend/app/(main)/admin/screenMng/popScreenMngList/page.tsx POP 관리

수정 (9개)

파일 변경 내용
backend-node/src/services/screenManagementService.ts POP CRUD 함수
backend-node/src/controllers/screenManagementController.ts 컨트롤러
backend-node/src/routes/screenManagementRoutes.ts 라우트
frontend/lib/api/screen.ts API 클라이언트
frontend/components/screen/CreateScreenModal.tsx isPop prop
frontend/components/screen/ScreenSettingModal.tsx isPop, PreviewTab
frontend/components/screen/ScreenRelationFlow.tsx isPop 전달
frontend/components/screen/ScreenDesigner.tsx isPop, 미리보기
frontend/components/screen/toolbar/SlimToolbar.tsx POP 미리보기 버튼

9. 반응형 전략 (신규 결정사항)

문제점

  • 데스크톱은 절대 좌표(position: { x, y }) 사용
  • 모바일 화면 크기가 달라지면 레이아웃 깨짐

결정: Flow 레이아웃 채택

항목 데스크톱 POP
배치 방식 position: { x, y } order: number (순서)
컨테이너 자유 배치 중첩 구조 (섹션 > 필드)
렌더러 절대 좌표 계산 Flexbox column (세로 쌓기)

Flow 레이아웃 데이터 구조

{
  layoutMode: "flow",  // flow | absolute
  components: [
    {
      id: "section-1",
      type: "pop-section",
      order: 0,  // 순서로 배치
      children: [...]
    }
  ]
}

10. POP 사용자 앱 구조 (신규 결정사항)

데스크톱 vs POP 진입 구조

데스크톱 POP
메뉴 왼쪽 사이드바 대시보드 카드
네비게이션 복잡한 트리 구조 화면 → 뒤로가기
URL /screens/{id} /pop/screens/{id}

POP 화면 흐름

/pop/login (POP 로그인)
    ↓
/pop/dashboard (화면 목록 - 카드형)
    ↓
/pop/screens/{id} (화면 뷰어)

11. POP 디자이너 (신규 계획)

진입 경로

popScreenMngList → [설계] 버튼 → PopDesigner 컴포넌트

레이아웃 구조 (2026-02-02 수정)

데스크톱 Screen Designer와 유사하게 좌측 탭 패널 + 우측 캔버스:

┌─────────────────────────────────────────────────────────────────┐
│  [툴바] ← 목록 | 화면명 | 📱모바일 📱태블릿 | 🔄 | 💾저장     │
├────────────────┬────────────────────────────────────────────────┤
│    [패널]      │              [캔버스 영역]                      │
│    ◀━━━━▶     │                                                │
│   (리사이즈)   │    ┌────────────────────────┐                 │
│                │    │   디바이스 프레임       │ ← 드래그로      │
│ ┌────────────┐ │    │                        │   팬 이동       │
│ │컴포넌트│편집│ │    │  [섹션 1]              │                 │
│ └────────────┘ │    │    ├─ 필드 A           │                 │
│                │    │    └─ 필드 B           │                 │
│  (컴포넌트 탭) │    │                        │                 │
│  📦 섹션       │    │  [섹션 2]              │                 │
│  📝 필드       │    │    ├─ 버튼1 ─ 버튼2    │                 │
│  🔘 버튼       │    │                        │                 │
│  📋 리스트     │    └────────────────────────┘                 │
│  📊 인디케이터 │                                                │
│                │                                                │
│  (편집 탭)     │                                                │
│  선택된 컴포   │                                                │
│  넌트 설정     │                                                │
└────────────────┴────────────────────────────────────────────────┘

패널 기능

기능 설명
리사이즈 드래그로 패널 너비 조절 (min: 200px, max: 400px)
컴포넌트 탭 POP 전용 컴포넌트만 표시
편집 탭 선택된 컴포넌트 설정 (프리셋 기반)

캔버스 기능

기능 설명
팬(Pan) 마우스 드래그로 보는 위치 이동
마우스 휠로 확대/축소 (선택사항)
디바이스 탭 📱모바일 / 📱태블릿 전환
나란히 보기 옵션으로 둘 다 표시 가능
실시간 미리보기 편집 = 미리보기 (별도 창 불필요)

캔버스 방식: 블록 쌓기

  • 섹션끼리는 위→아래로 쌓임
  • 섹션 안에서는 가로(row) 또는 세로(column) 선택 가능
  • 드래그앤드롭으로 순서 변경
  • 캔버스 자체가 실시간 미리보기

기준 해상도

디바이스 논리적 크기 (dp) 용도
모바일 360 x 640 Zebra TC52/57 등 산업용 핸드헬드
태블릿 768 x 1024 8~10인치 산업용 태블릿

터치 타겟 (장갑 착용 고려)

  • 최소 버튼 크기: 60dp (일반 앱 48dp보다 큼)
  • 버튼 간격: 16dp 이상

반응형 편집 방식

모드 설명
기준 디바이스 태블릿 (메인 편집)
자동 조정 CSS flex-wrap, grid로 모바일 자동 줄바꿈
수동 조정 모바일 탭에서 그리드 열 수, 숨기기 설정

흐름:

1. 태블릿 탭에서 편집 (기준)
   → 모든 컴포넌트, 섹션, 순서, 데이터 바인딩 설정

2. 모바일 탭에서 확인
   A) 자동 조정 OK → 그대로 저장
   B) 배치 어색함 → 그리드 열 수 조정 또는 숨기기

섹션 내 컴포넌트 배치 옵션

설정 옵션
배치 방향 row / column
순서 드래그로 변경
비율 flex (1:1, 2:1, 1:2 등)
정렬 start / center / end
간격 none / small / medium / large
줄바꿈 wrap / nowrap
그리드 열 수 태블릿용, 모바일용 각각 설정 가능

관리자가 설정 가능한 것

항목 설정 방식
섹션 순서 드래그로 위/아래 이동
섹션 내 배치 가로(row) / 세로(column)
정렬 왼쪽/가운데/오른쪽, 위/가운데/아래
컴포넌트 비율 1:1, 2:1, 1:2 등 (flex)
크기 S/M/L/XL 프리셋
여백/간격 작음/보통/넓음 프리셋
아이콘 선택 가능
테마/색상 프리셋 또는 커스텀
그리드 열 수 태블릿/모바일 각각
모바일 숨기기 특정 컴포넌트 숨김

관리자가 설정 불가능한 것 (반응형 유지)

  • 정확한 x, y 좌표
  • 정확한 픽셀 크기 (예: 347px)
  • 고정 위치 (예: 왼쪽에서 100px)

스타일 분리 원칙

뼈대 (변경 어려움 - 처음부터 잘 설계):
- 데이터 바인딩 구조 (columnName, dataSource)
- 컴포넌트 계층 (섹션 > 필드)
- 액션 로직

옷 (변경 쉬움 - 나중에 조정 가능):
- 색상, 폰트 크기 → CSS 변수/테마
- 버튼 모양 → 프리셋
- 아이콘 → 선택

다국어 연동 (준비)

  • 상태: showMultilangSettingsModal 미리 추가
  • 버튼: 툴바에 자리만 (비활성)
  • 연결: 추후 MultilangSettingsModal import

데스크톱 시스템 재사용

기능 재사용 비고
formData 관리 O 그대로
필드간 연결 O cascading, hierarchy
테이블 참조 O dataSource, filter
저장 이벤트 O beforeFormSave
집계 O 스타일만 변경
설정 패널 O 탭 방식 참고
CRUD API O 그대로
buttonActions O 그대로
다국어 O MultilangSettingsModal

파일 구조 (신규 생성 예정)

frontend/components/pop/
├── PopDesigner.tsx          # 메인 (좌: 패널, 우: 캔버스)
├── PopCanvas.tsx            # 캔버스 (팬/줌 + 프레임)
├── PopToolbar.tsx           # 상단 툴바
│
├── panels/
│   └── PopPanel.tsx         # 통합 패널 (컴포넌트/편집 탭)
│
├── components/              # POP 전용 컴포넌트
│   ├── PopSection.tsx
│   ├── PopField.tsx
│   ├── PopButton.tsx
│   └── ...
│
└── types/
    └── pop-layout.ts        # PopLayoutData, PopComponentData

12. POP 레이아웃 데이터 구조 (신규)

PopLayoutData

interface PopLayoutData {
  version: "pop-1.0";
  layoutMode: "flow";  // 항상 flow (절대좌표 없음)
  deviceTarget: "mobile" | "tablet" | "both";
  components: PopComponentData[];
}

PopComponentData

interface PopComponentData {
  id: string;
  type: "pop-section" | "pop-field" | "pop-button" | "pop-list" | "pop-indicator";
  order: number;  // 순서 (x, y 좌표 대신)
  
  // 개별 컴포넌트 flex 비율
  flex?: number;  // 기본 1
  
  // 섹션인 경우: 내부 레이아웃 설정
  layout?: {
    direction: "row" | "column";
    justify: "start" | "center" | "end" | "between";
    align: "start" | "center" | "end";
    gap: "none" | "small" | "medium" | "large";
    wrap: boolean;
    grid?: number;  // 태블릿 기준 열 수
  };
  
  // 크기 프리셋
  size?: "S" | "M" | "L" | "XL" | "full";
  
  // 데이터 바인딩
  dataBinding?: {
    tableName: string;
    columnName: string;
    displayField?: string;
  };
  
  // 스타일 프리셋
  style?: {
    variant: "default" | "primary" | "success" | "warning" | "danger";
    padding: "none" | "small" | "medium" | "large";
  };
  
  // 모바일 오버라이드 (선택사항)
  mobileOverride?: {
    grid?: number;      // 모바일 열 수 (없으면 자동)
    hidden?: boolean;   // 모바일에서 숨기기
  };
  
  // 하위 컴포넌트 (섹션 내부)
  children?: PopComponentData[];
  
  // 컴포넌트별 설정
  config?: Record<string, any>;
}

데스크톱 vs POP 데이터 비교

항목 데스크톱 (LayoutData) POP (PopLayoutData)
배치 position: { x, y, z } order: number
크기 size: { width, height } (픽셀) `size: "S"
컨테이너 없음 (자유 배치) layout: { direction, grid }
반응형 없음 mobileOverride

13. 컴포넌트 재사용성 분석

최종 분류

분류 개수 컴포넌트
완전 재사용 2 form-field, action-button
부분 재사용 4 tab-panel, data-table, kpi-gauge, process-flow
신규 개발 7 section, card-list, status-indicator, number-pad, barcode-scanner, timer, alarm-list

핵심 컴포넌트 7개 (최소 필수)

컴포넌트 역할 포함 기능
pop-section 레이아웃 컨테이너 카드, 그룹핑, 접기/펼치기
pop-field 데이터 입력/표시 텍스트, 숫자, 드롭다운, 바코드, 숫자패드
pop-button 액션 실행 저장, 삭제, API 호출, 화면이동
pop-list 데이터 목록 카드리스트, 선택목록, 테이블 참조
pop-indicator 상태/수치 표시 KPI, 게이지, 신호등, 진행률
pop-scanner 바코드/QR 입력 카메라, 외부 스캐너
pop-numpad 숫자 입력 특화 큰 버튼, 계산기 모드

TODO

Phase 1: POP 디자이너 개발 (현재 진행)

# 작업 설명 상태
1 PopLayoutData 타입 정의 order, layout, mobileOverride 완료
2 PopDesigner.tsx 좌: 리사이즈 패널, 우: 팬 가능 캔버스 완료
3 PopPanel.tsx 탭 (컴포넌트/편집), POP 컴포넌트만 완료
4 PopCanvas.tsx 팬/줌 + 디바이스 프레임 + 블록 렌더링 완료
5 SectionGrid.tsx 섹션 내부 컴포넌트 배치 (react-grid-layout) 완료
6 드래그앤드롭 팔레트→캔버스 (섹션), 팔레트→섹션 (컴포넌트) 완료
7 컴포넌트 자유 배치/리사이즈 고정 셀 크기(40px) 기반 자동 그리드 완료
8 편집 탭 그리드 설정, 모바일 오버라이드 완료 (기본)
9 저장/로드 기존 API 재사용 (saveLayoutPop) 완료

Phase 2: POP 컴포넌트 개발

상세: docs/pop/components-spec.md

1단계 (우선):

  • pop-section (레이아웃 컨테이너)
  • pop-field (범용 입력)
  • pop-button (액션)

2단계:

  • pop-list (카드형 목록)
  • pop-indicator (상태/KPI)
  • pop-numpad (숫자패드)

3단계:

  • pop-scanner (바코드)
  • pop-timer (타이머)
  • pop-alarm (알람)

Phase 3: POP 사용자 앱

  • /pop/login - POP 전용 로그인
  • /pop/dashboard - 화면 목록 (카드형)
  • /pop/screens/[id] - Flow 렌더러 적용

기타

  • POP 컴포넌트 레지스트리
  • POP 메뉴/폴더 관리
  • POP 인증 분리
  • 다국어 연동

핵심 파일 참조

기존 파일 (참고용)

파일 용도
frontend/app/(main)/admin/screenMng/popScreenMngList/page.tsx 진입점, PopDesigner 호출 위치
frontend/components/screen/ScreenDesigner.tsx 데스크톱 디자이너 (구조 참고)
frontend/components/screen/modals/MultilangSettingsModal.tsx 다국어 모달 (추후 연동)
frontend/lib/api/screen.ts API (getLayoutPop, saveLayoutPop)
backend-node/src/services/screenManagementService.ts POP CRUD (4720~4920행)

신규 생성 예정

파일 용도
frontend/components/pop/PopDesigner.tsx 메인 디자이너
frontend/components/pop/PopCanvas.tsx 캔버스 (팬/줌)
frontend/components/pop/PopToolbar.tsx 툴바
frontend/components/pop/panels/PopPanel.tsx 통합 패널
frontend/components/pop/types/pop-layout.ts 타입 정의
frontend/components/pop/components/PopSection.tsx 섹션 컴포넌트


14. 그리드 시스템 단순화 (2026-02-02 변경)

기존 문제: 이중 그리드 구조

캔버스 (24열, rowHeight 20px)
  └─ 섹션 (colSpan/rowSpan으로 크기 지정)
       └─ 내부 그리드 (columns/rows로 컴포넌트 배치)

문제점:

  1. 섹션 크기와 내부 그리드가 독립적이라 동기화 안됨
  2. 섹션을 늘려도 내부 그리드 점은 그대로 (비례 확대만)
  3. 사용자가 두 가지 단위를 이해해야 함

변경: 단일 자동계산 그리드

핵심 변경사항:

  • 그리드 점(dot) 제거
  • 고정 셀 크기(40px) 기반으로 섹션 크기에 따라 열/행 수 자동 계산
  • 컴포넌트는 react-grid-layout으로 자유롭게 드래그/리사이즈

코드 (SectionGrid.tsx):

const CELL_SIZE = 40;
const cols = Math.max(1, Math.floor((availableWidth + gap) / (CELL_SIZE + gap)));
const rows = Math.max(1, Math.floor((availableHeight + gap) / (CELL_SIZE + gap)));

결과:

  • 섹션 크기 변경 → 내부 셀 개수 자동 조정
  • 컴포넌트 자유 배치/리사이즈 가능
  • 직관적인 사용자 경험

onLayoutChange 대신 onDragStop/onResizeStop 사용

문제: onLayoutChange는 드롭 직후에도 호출되어 섹션 크기가 자동 확대됨

해결:

// 변경 전
<GridLayout onLayoutChange={handleLayoutChange} ... />

// 변경 후
<GridLayout onDragStop={handleDragResizeStop} onResizeStop={handleDragResizeStop} ... />

상태 업데이트는 드래그/리사이즈 완료 후에만 실행


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