From f825d65bfccf9ea9f2530899860bde4c12733f87 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 9 Feb 2026 19:31:46 +0900 Subject: [PATCH] =?UTF-8?q?feat(pop):=20=EB=B7=B0=EC=96=B4=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A0=88=EC=A7=80=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - page.tsx: overflow-hidden 제거로 뷰어 스크롤 활성화, pop-components 레지스트리 자동 등록 import 추가 - PopRenderer.tsx: 레지스트리에서 실제 컴포넌트 조회 후 렌더링, 미등록 컴포넌트는 플레이스홀더 fallback 표시 - PLAN.MD: POP 뷰어 스크롤 수정 계획으로 업데이트 - POPUPDATE_2.md: v8.0 - 모달 화면 설계 규칙(제9조) 추가, 버튼 modal action 스펙 확장 (inline/screen-ref 모드) Co-authored-by: Cursor --- PLAN.MD | 259 +++++++++-------- POPUPDATE_2.md | 273 +++++++++++++++++- .../app/(pop)/pop/screens/[screenId]/page.tsx | 8 +- .../pop/designer/renderers/PopRenderer.tsx | 17 +- 4 files changed, 418 insertions(+), 139 deletions(-) diff --git a/PLAN.MD b/PLAN.MD index 0eff7965..e4f4e424 100644 --- a/PLAN.MD +++ b/PLAN.MD @@ -1,139 +1,156 @@ -# 프로젝트: V2/V2 컴포넌트 설정 스키마 정비 +# 현재 구현 계획: POP 뷰어 스크롤 수정 -## 개요 - -레거시 컴포넌트를 제거하고, V2/V2 컴포넌트 전용 Zod 스키마와 기본값 레지스트리를 한 곳에서 관리한다. - -## 핵심 기능 - -1. [x] 레거시 컴포넌트 스키마 제거 -2. [x] V2 컴포넌트 overrides 스키마 정의 (16개) -3. [x] V2 컴포넌트 overrides 스키마 정의 (9개) -4. [x] componentConfig.ts 한 파일에서 통합 관리 - -## 정의된 V2 컴포넌트 (18개) - -- v2-table-list, v2-button-primary, v2-text-display -- v2-split-panel-layout, v2-section-card, v2-section-paper -- v2-divider-line, v2-repeat-container, v2-rack-structure -- v2-numbering-rule, v2-category-manager, v2-pivot-grid -- v2-location-swap-selector, v2-aggregation-widget -- v2-card-display, v2-table-search-widget, v2-tabs-widget -- v2-v2-repeater - -## 정의된 V2 컴포넌트 (9개) - -- v2-input, v2-select, v2-date -- v2-list, v2-layout, v2-group -- v2-media, v2-biz, v2-hierarchy - -## 테스트 계획 - -### 1단계: 기본 기능 - -- [x] V2 레이아웃 저장 시 컴포넌트별 overrides 스키마 검증 통과 -- [x] V2 컴포넌트 기본값과 스키마가 매칭됨 - -### 2단계: 에러 케이스 - -- [x] 잘못된 overrides 입력 시 Zod 검증 실패 처리 (safeParse + console.warn + graceful fallback) -- [x] 누락된 기본값 컴포넌트 저장 시 안전한 기본값 적용 (레지스트리 조회 → 빈 객체) - -## 에러 처리 계획 - -- 스키마 파싱 실패 시 로그/에러 메시지 표준화 -- 기본값 누락 시 안전한 fallback 적용 - -## 진행 상태 - -- [x] 레거시 컴포넌트 제거 완료 -- [x] V2/V2 스키마 정의 완료 -- [x] 한 파일 통합 관리 완료 - -# 프로젝트: 화면 복제 기능 개선 (DB 구조 개편 후) - -## 개요 - -채번/카테고리에서 `menu_objid` 의존성 제거 완료 후, 화면 복제 기능을 새 DB 구조에 맞게 수정하고 테스트합니다. - -## 핵심 변경사항 - -### DB 구조 변경 (완료) - -- 채번규칙: `menu_objid` 의존성 제거 → `table_name + column_name + company_code` 기반 -- 카테고리: `menu_objid` 의존성 제거 → `table_name + column_name + company_code` 기반 -- 복제 순서 의존성 문제 해결 - -### 복제 옵션 정리 (완료) - -- [x] **삭제**: 코드 카테고리 + 코드 복사 옵션 -- [x] **삭제**: 연쇄관계 설정 복사 옵션 -- [x] **이름 변경**: "카테고리 매핑 + 값 복사" → "카테고리 값 복사" - -### 현재 복제 옵션 (3개) - -1. **채번 규칙 복사** - 채번규칙 복제 -2. **카테고리 값 복사** - 카테고리 값 복제 (table_column_category_values) -3. **테이블 타입관리 입력타입 설정 복사** - table_type_columns 복제 +> **작성일**: 2026-02-09 +> **상태**: 계획 완료, 코딩 대기 +> **목적**: 뷰어에서 화면 높이를 초과하는 컴포넌트가 잘리지 않고 스크롤 가능하도록 수정 --- -## 테스트 계획 +## 1. 문제 요약 -### 1. 화면 간 연결 복제 테스트 +설계(디자이너)에서 컴포넌트를 아래로 배치하면 캔버스가 늘어나고 스크롤이 되지만, +뷰어(`/pop/screens/4114`)에서는 화면 높이를 초과하는 컴포넌트가 잘려서 안 보임. -- [ ] 수주관리 1번→2번→3번→4번 화면 연결 상태에서 복제 -- [ ] 복제 후 연결 관계가 유지되는지 확인 -- [ ] 각 화면의 고유 키값이 새로운 화면을 참조하도록 변경되는지 확인 +**근본 원인**: CSS 컨테이너 구조가 스크롤을 차단 -### 2. 제어관리 복제 테스트 - -- [ ] 다른 회사로 제어관리 복제 -- [ ] 복제된 플로우 스텝/연결이 정상 작동하는지 확인 - -### 3. 추가 옵션 복제 테스트 - -- [ ] 채번규칙 복사 정상 작동 확인 -- [ ] 카테고리 값 복사 정상 작동 확인 -- [ ] 테이블 타입관리 입력타입 설정 복사 정상 작동 확인 - -### 4. 기본 복제 테스트 - -- [ ] 단일 화면 복제 (모달 포함) -- [ ] 그룹 전체 복제 (재귀적) -- [ ] 메뉴 동기화 정상 작동 +| # | 컨테이너 (라인) | 현재 클래스 | 문제 | +|---|----------------|-------------|------| +| 1 | 최외곽 (185) | `h-screen ... overflow-hidden` | 넘치는 콘텐츠를 잘라냄 | +| 2 | 컨텐츠 영역 (266) | 일반 모드에 `overflow-auto` 없음 | 스크롤 불가 | +| 3 | 백색 배경 (275) | 일반 모드에 `min-h-full` 없음 | 짧은 콘텐츠 시 배경 불완전 | --- -## 관련 파일 +## 2. 수정 대상 파일 (1개) -- `frontend/components/screen/CopyScreenModal.tsx` - 복제 모달 -- `frontend/components/screen/ScreenGroupTreeView.tsx` - 트리 뷰 + 컨텍스트 메뉴 -- `backend-node/src/services/screenManagementService.ts` - 복제 서비스 -- `backend-node/src/services/numberingRuleService.ts` - 채번규칙 서비스 -- `docs/DB_STRUCTURE_DIAGRAM.md` - DB 구조 문서 +### `frontend/app/(pop)/pop/screens/[screenId]/page.tsx` -## 진행 상태 +**변경 유형**: CSS 클래스 문자열 수정 3곳 (새 변수/함수/타입 추가 없음) + +#### 변경 1: 라인 185 - 최외곽 컨테이너 + +**현재 코드**: +``` +
+``` + +**변경 코드**: +``` +
+``` + +**변경 내용**: `overflow-hidden` 제거 +**이유**: 이 div는 프리뷰 툴바 + 컨텐츠의 flex 컨테이너 역할만 하면 됨. `overflow-hidden`이 자식의 스크롤까지 차단하므로 제거 + +#### 변경 2: 라인 266 - 컨텐츠 영역 + +**현재 코드**: +``` +
+``` + +**변경 코드**: +``` +
+``` + +**변경 내용**: `overflow-auto`를 조건문 밖으로 이동 (공통 적용) +**이유**: 프리뷰/일반 모드 모두 스크롤이 필요함 + +#### 변경 3: 라인 275 - 백색 배경 컨테이너 + +**현재 코드**: +``` +className={`bg-white transition-all duration-300 ${isPreviewMode ? "shadow-2xl rounded-3xl overflow-auto border-8 border-gray-800" : "w-full"}`} +``` + +**변경 코드**: +``` +className={`bg-white transition-all duration-300 ${isPreviewMode ? "shadow-2xl rounded-3xl overflow-auto border-8 border-gray-800" : "w-full min-h-full"}`} +``` + +**변경 내용**: 일반 모드에 `min-h-full` 추가 +**이유**: 컴포넌트가 적어 콘텐츠가 짧을 때에도 흰색 배경이 화면 전체를 채우도록 보장 + +--- + +## 3. 구현 순서 (의존성 기반) + +| 순서 | 작업 | 라인 | 의존성 | 상태 | +|------|------|------|--------|------| +| 1 | 라인 185: `overflow-hidden` 제거 | 185 | 없음 | [x] 완료 | +| 2 | 라인 266: `overflow-auto` 공통 적용 | 266 | 순서 1 | [x] 완료 | +| 3 | 라인 275: 일반 모드 `min-h-full` 추가 | 275 | 순서 2 | [x] 완료 | +| 4 | 린트 검사 | - | 순서 1~3 | [x] 통과 | +| 5 | 브라우저 검증 | - | 순서 4 | [ ] 대기 | + +--- + +## 4. 사전 충돌 검사 결과 + +**새로 추가할 변수/함수/타입: 없음** + +이번 수정은 기존 Tailwind CSS 클래스 문자열만 변경합니다. +새로운 식별자(변수, 함수, 타입)를 추가하지 않으므로 충돌 검사 대상이 없습니다. + +--- + +## 5. 에러 함정 경고 + +### 함정 1: 순서 1만 하고 순서 2를 빼먹으면 +`overflow-hidden`만 제거하면 콘텐츠가 화면 밖으로 넘쳐 보이지만 스크롤은 안 됨. +부모는 열었지만 자식에 스크롤 속성이 없는 상태. + +### 함정 2: 순서 2만 하고 순서 1을 빼먹으면 +자식에 `overflow-auto`를 넣어도 부모가 `overflow-hidden`으로 잘라내므로 여전히 스크롤 안 됨. +**반드시 순서 1과 2를 함께 적용해야 함.** + +### 함정 3: 프리뷰 모드 영향 +프리뷰 모드는 이미 자체적으로 `overflow-auto`가 있으므로 이 수정에 영향 없음. +`overflow-auto`가 중복 적용되어도 CSS에서 문제 없음. + +--- + +## 6. 검증 방법 + +1. `localhost:9771/pop/screens/4114` 접속 (iPhone SE 375px 기준) +2. 화면 아래로 스크롤 가능한지 확인 +3. 맨 아래에 이미지(pop-text 5, 6)가 보이는지 확인 +4. 프리뷰 모드(`?preview=true`)에서도 기존처럼 정상 동작하는지 확인 +5. 컴포넌트가 적은 화면에서 흰색 배경이 화면 전체를 채우는지 확인 + +--- + +## 이전 완료 계획 (아카이브) + +
+POP 뷰어 실제 컴포넌트 렌더링 (완료) + +- [x] 뷰어 페이지에 레지스트리 초기화 import 추가 +- [x] `renderActualComponent()` 실제 컴포넌트 렌더링으로 교체 +- [x] 린트 검사 통과 +- 브라우저 검증: 컴포넌트 표시 정상, 스크롤 문제 발견 -> 별도 수정 + +
+ +
+V2/V2 컴포넌트 설정 스키마 정비 (완료) + +- [x] 레거시 컴포넌트 스키마 제거 +- [x] V2 컴포넌트 overrides 스키마 정의 (16개) +- [x] V2 컴포넌트 overrides 스키마 정의 (9개) +- [x] componentConfig.ts 한 파일에서 통합 관리 + +
+ +
+화면 복제 기능 개선 (진행 중) - [완료] DB 구조 개편 (menu_objid 의존성 제거) -- [완료] 복제 옵션 정리 (코드카테고리/연쇄관계 삭제, 이름 변경) -- [완료] 화면 간 연결 복제 버그 수정 (targetScreenId 매핑 추가) +- [완료] 복제 옵션 정리 +- [완료] 화면 간 연결 복제 버그 수정 - [대기] 화면 간 연결 복제 테스트 - [대기] 제어관리 복제 테스트 - [대기] 추가 옵션 복제 테스트 ---- - -## 수정 이력 - -### 2026-01-26: 버튼 targetScreenId 매핑 버그 수정 - -**문제**: 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않음 - -- 수주관리 1→2→3→4 화면 복제 시 연결이 깨지는 문제 - -**수정 파일**: `backend-node/src/services/screenManagementService.ts` - -- `updateTabScreenReferences` 함수에 `targetScreenId` 처리 로직 추가 -- 쿼리에 `targetScreenId` 검색 조건 추가 -- 문자열/숫자 타입 모두 처리 +
diff --git a/POPUPDATE_2.md b/POPUPDATE_2.md index cc08d883..c4da5c4e 100644 --- a/POPUPDATE_2.md +++ b/POPUPDATE_2.md @@ -1,4 +1,4 @@ -# POP 컴포넌트 정의서 v7.0 +# POP 컴포넌트 정의서 v8.0 ## POP 헌법 (공통 규칙) @@ -50,6 +50,14 @@ - 모든 컴포넌트는 레지스트리에 등록해야 디자이너에 나타난다 - 모든 컴포넌트 인스턴스는 userConfigurable, displayName 공통 속성을 가진다 +### 제9조. 모달 화면의 설계 + +- 모달은 인라인(컴포넌트 설정만으로 구성)과 외부 참조(별도 POP 화면 연결) 두 가지 방식이 있다 +- 단순한 목록 선택은 인라인 모달을 사용한다 (설정만으로 완결) +- 복잡한 검색/필터가 필요하거나 여러 곳에서 재사용하는 모달은 별도 POP 화면을 만들어 참조한다 +- 모달 안의 화면도 동일한 POP 컴포넌트 시스템으로 구성된다 (같은 그리드, 같은 컴포넌트) +- 모달 화면의 layout_data는 기존 screen_layouts_pop 테이블에 저장한다 (DB 변경 불필요) + --- ## 현재 상태 @@ -203,7 +211,12 @@ DataSourceConfig를 받아서 기존 API를 호출하고 결과를 반환: - `type`: "navigate" | "modal" | "save" | "delete" | "api" | "event" | "refresh" - `navigate`: { screenId, url } -- `modal`: { title, dataSource } +- `modal`: { mode, title, screenId, inlineConfig, modalSize } + - mode: "inline" (설정만으로 구성) | "screen-ref" (별도 화면 참조) + - title: 모달 제목 + - screenId: mode가 "screen-ref"일 때 참조할 POP 화면 ID + - inlineConfig: mode가 "inline"일 때 사용할 DataSourceConfig + 표시 설정 + - modalSize: { width, height } 모달 크기 - `save`: { targetColumns } - `delete`: { confirmMessage } - `api`: { method, endpoint, body } @@ -224,22 +237,97 @@ DataSourceConfig를 받아서 기존 API를 호출하고 결과를 반환: - **이벤트**: 발행 없음, 수신 없음 - **설정**: 내용, 폰트 크기/굵기, 좌우/상하 정렬, 이미지 URL/맞춤/크기, 날짜 포맷 빌더 -### 2. pop-dashboard (신규) +### 2. pop-dashboard (신규 - 2026-02-09 토의 결과 반영) -- **한 줄 정의**: 숫자를 집계해서 보여줌 +- **한 줄 정의**: 여러 집계 아이템을 묶어서 다양한 방식으로 보여줌 - **카테고리**: display -- **역할**: 숫자 데이터를 집계/계산하여 시각화 -- **서브타입**: +- **역할**: 숫자 데이터를 집계/계산하여 시각화. 하나의 컴포넌트 안에 여러 집계 아이템을 담는 컨테이너 +- **구조**: 1개 pop-dashboard = 여러 DashboardItem의 묶음. 각 아이템은 독립적으로 데이터 소스/서브타입/보이기숨기기 설정 가능 +- **서브타입** (아이템별로 선택, 한 묶음에 혼합 가능): - kpi-card: 숫자 + 단위 + 라벨 + 증감 표시 - chart: 막대/원형/라인 차트 - gauge: 게이지 (목표 대비 달성률) - stat-card: 통계 카드 (건수 + 대기 + 링크) -- **데이터**: DataSourceConfig (조인/집계 자유) +- **표시 모드** (디자이너가 선택): + - arrows: 좌우 버튼으로 아이템 넘기기 + - auto-slide: 전광판처럼 자동 전환 (터치 시 멈춤, 일정 시간 후 재개) + - grid: 컴포넌트 영역 내부를 행/열로 쪼개서 여러 아이템 동시 표시 (디자이너가 각 아이템 위치 직접 지정) + - scroll: 좌우 또는 상하 스와이프 +- **데이터**: 각 아이템별 독립 DataSourceConfig (조인/집계 자유) +- **계산식 지원**: "생산량/총재고량", "출고량/현재고량" 같은 복합 표현 가능 + - 값 A, B를 각각 다른 테이블/집계로 설정 + - 표시 형태: 분수(1,234/5,678), 퍼센트(21.7%), 비율(1,234:5,678) - **CRUD**: 주로 읽기. 목표값 수정 등 필요 시 write 컬럼으로 저장 가능 - **이벤트**: - 수신: filter_changed, data_ready - - 발행: kpi_clicked (KPI 카드 클릭 시 상세 데이터 전달) -- **설정**: 데이터 소스, 집계 함수, 라벨, 단위, 색상 구간, 차트 타입, 새로고침 주기, 목표값, 표시 모드(slide/scroll/grid) + - 발행: kpi_clicked (아이템 클릭 시 상세 데이터 전달) +- **설정**: 데이터 소스(드롭다운 기반 쉬운 집계), 집계 함수, 계산식, 라벨, 단위, 색상 구간, 차트 타입, 새로고침 주기, 목표값, 표시 모드, 아이템별 보이기/숨기기 +- **보이기/숨기기**: 각 아이템별로 pop-system에서 개별 on/off 가능 (userConfigurable) +- **기존 POP 대시보드 폐기**: `frontend/components/pop/dashboard/` 폴더 전체를 이 컴포넌트로 대체 예정 (Phase 1~3 완료 후) + +#### pop-dashboard 데이터 구조 + +``` +PopDashboardConfig { + items: DashboardItem[] // 아이템 목록 (각각 독립 설정) + displayMode: "arrows" | "auto-slide" | "grid" | "scroll" + autoSlideInterval: number // 자동 슬라이드 간격(초) + gridLayout: { columns: number, rows: number } // 행열 그리드 설정 + showIndicator: boolean // 페이지 인디케이터 표시 + gap: number // 아이템 간 간격 +} + +DashboardItem { + id: string + label: string // pop-system에서 보이기/숨기기용 이름 + visible: boolean // 보이기/숨기기 + subType: "kpi-card" | "chart" | "gauge" | "stat-card" + dataSource: DataSourceConfig // 각 아이템별 독립 데이터 소스 + + // 행열 그리드 모드에서의 위치 (디자이너가 직접 지정) + gridPosition: { col: number, row: number, colSpan: number, rowSpan: number } + + // 계산식 (선택사항) + formula?: { + enabled: boolean + values: [ + { id: "A", dataSource: DataSourceConfig, label: "생산량" }, + { id: "B", dataSource: DataSourceConfig, label: "총재고량" }, + ] + expression: string // "A / B", "A + B", "A / B * 100" + displayFormat: "value" | "fraction" | "percent" | "ratio" + } + + // 서브타입별 설정 + kpiConfig?: { unit, colorRanges, showTrend, trendPeriod } + chartConfig?: { chartType, xAxis, yAxis, colors } + gaugeConfig?: { min, max, target, colorRanges } + statConfig?: { categories, showLink } +} +``` + +#### 설정 패널 흐름 (드롭다운 기반 쉬운 집계) + +``` +1. [+ 아이템 추가] 버튼 클릭 +2. 서브타입 선택: kpi-card / chart / gauge / stat-card +3. 데이터 모드 선택: [단일 집계] 또는 [계산식] + + [단일 집계] + - 테이블 선택 (table-schema API로 목록) + - 조인할 테이블 추가 (선택사항) + - 컬럼 선택 → 집계 함수 선택 (합계/건수/평균/최소/최대) + - 필터 조건 추가 + + [계산식] (예: 생산량/총재고량) + - 값 A: 테이블 -> 컬럼 -> 집계함수 + - 값 B: 테이블 -> 컬럼 -> 집계함수 (다른 테이블도 가능) + - 계산식: A / B + - 표시 형태: 분수 / 퍼센트 / 비율 + +4. 라벨, 단위, 색상 등 외형 설정 +5. 행열 그리드 위치 설정 (grid 모드일 때) +``` ### 3. pop-table (신규 - 가장 복잡) @@ -335,6 +423,59 @@ DataSourceConfig를 받아서 기존 API를 호출하고 결과를 반환: - **pop-icon과의 차이**: pop-icon은 이동/실행만 하고 값이 안 돌아옴. pop-lookup은 값을 골라서 돌려줌 - **pop-search와의 차이**: pop-search는 텍스트/날짜/드롭다운으로 필터링. pop-lookup은 모달을 열어서 목록에서 선택 +#### pop-lookup 모달 화면 설계 방식 + +pop-lookup이 열리는 모달의 내부 화면은 **두 가지 방식** 중 선택할 수 있다: + +**방식 A: 인라인 모달 (기본)** +- pop-lookup 컴포넌트의 설정 패널에서 직접 모달 내부 화면을 구성 +- DataSourceConfig + 표시 컬럼 + 검색 필터 설정만으로 동작 +- 별도 화면 생성 없이 컴포넌트 설정만으로 완결 +- 적합한 경우: 단순 목록 선택 (거래처 목록, 품목 목록 등) + +**방식 B: 외부 화면 참조 (고급)** +- 별도의 POP 화면(screen_id)을 모달로 연결 +- 모달 안에서 검색/필터/테이블 등 복잡한 화면을 디자이너로 자유롭게 구성 +- 여러 pop-lookup에서 같은 모달 화면을 재사용 가능 +- 적합한 경우: 복잡한 검색/필터가 필요한 선택 화면, 여러 화면에서 공유하는 모달 + +**설정 구조:** + +``` +modalConfig: { + mode: "inline" | "screen-ref" + + // mode = "inline"일 때 사용 + dataSource: DataSourceConfig + displayColumns: ColumnBinding[] + searchFilter: { enabled: boolean, targetColumns: string[] } + modalSize: { width: number, height: number } + + // mode = "screen-ref"일 때 사용 + screenId: number // 참조할 POP 화면 ID + returnMapping: { // 모달 화면에서 선택된 값을 어떻게 매핑할지 + sourceColumn: string // 모달 화면에서 반환하는 컬럼 + targetField: string // pop-lookup 필드에 표시할 값 + }[] + modalSize: { width: number, height: number } +} +``` + +**기존 시스템과의 호환성 (검증 완료):** + +| 항목 | 현재 상태 | pop-lookup 지원 여부 | +|------|-----------|---------------------| +| DB: layout_data JSONB | 유연한 JSON 구조 | modalConfig를 layout_data에 저장 가능 (스키마 변경 불필요) | +| DB: screen_layouts_pop 테이블 | screen_id + company_code 기반 | 모달 화면도 별도 screen_id로 저장 가능 | +| 프론트: TabsWidget | screenId로 외부 화면 참조 지원 | 같은 패턴으로 모달에서 외부 화면 로드 가능 | +| 프론트: detectLinkedModals API | 연결된 모달 화면 감지 기능 있음 | 화면 간 참조 관계 추적에 활용 가능 | +| 백엔드: saveLayoutPop/getLayoutPop | POP 전용 저장/조회 API 있음 | 모달 화면도 동일 API로 저장/조회 가능 | +| 레이어 시스템 | layer_id 기반 다중 레이어 지원 | 모달 내부 레이아웃을 레이어로 관리 가능 | + +**DB 마이그레이션 불필요**: layout_data가 JSONB이므로 modalConfig를 컴포넌트 overrides에 포함하면 됨. +**백엔드 변경 불필요**: 기존 saveLayoutPop/getLayoutPop API가 그대로 사용 가능. +**프론트엔드 참고 패턴**: TabsWidget의 screenId 참조 방식을 그대로 차용. + ### 9. pop-system (신규) - **한 줄 정의**: 시스템 설정을 하나로 통합한 컴포넌트 (프로필, 테마, 보이기/숨기기) @@ -413,7 +554,34 @@ sequenceDiagram Note over Table: 발주 품목 3건 표시 ``` -### 예시 4: 컬럼별 읽기/쓰기 분리 동작 +### 예시 4: pop-lookup 인라인 모달 vs 외부 화면 참조 + +```mermaid +sequenceDiagram + participant User as 사용자 + participant Lookup as pop-lookup (거래처) + participant Modal as 모달 + + Note over User,Modal: [방식 A: 인라인 모달] + User->>Lookup: 거래처 필드 클릭 + Lookup->>Modal: 인라인 모달 열림 (DataSourceConfig 기반) + Note over Modal: supplier 테이블에서 목록 조회 + Note over Modal: 테이블형 목록 표시 + User->>Modal: "대한금속" 선택 + Modal->>Lookup: value_selected { supplier_code: "DH001", name: "대한금속" } + Note over Lookup: 필드에 "대한금속" 표시 + + Note over User,Modal: [방식 B: 외부 화면 참조] + User->>Lookup: 거래처 필드 클릭 + Lookup->>Modal: 모달 열림 (screenId=42 화면 로드) + Note over Modal: 별도 POP 화면 렌더링 + Note over Modal: pop-search(검색) + pop-table(목록) 등 배치된 컴포넌트 동작 + User->>Modal: 검색 후 "대한금속" 선택 + Modal->>Lookup: returnMapping 기반으로 값 반환 + Note over Lookup: 필드에 "대한금속" 표시 +``` + +### 예시 5: 컬럼별 읽기/쓰기 분리 동작 5개 컬럼이 있는 발주 화면: @@ -431,13 +599,91 @@ sequenceDiagram ## 구현 우선순위 - Phase 0 (공통 인프라): ColumnBinding, JoinConfig, DataSourceConfig 타입, useDataSource 훅 (CRUD 포함), usePopEvent 훅 (데이터 전달 포함), PopActionConfig 타입 -- Phase 1 (기본 표시): pop-dashboard (KPI 카드 서브타입부터) +- Phase 1 (기본 표시): pop-dashboard (4개 서브타입 전부 + 멀티 아이템 컨테이너 + 4개 표시 모드 + 계산식) - Phase 2 (기본 액션): pop-button, pop-icon - Phase 3 (데이터 목록): pop-table (테이블형부터, 카드형은 후순위) - Phase 4 (입력/연동): pop-search, pop-field, pop-lookup -- Phase 5 (고도화): pop-table 카드 템플릿, 차트, 게이지 +- Phase 5 (고도화): pop-table 카드 템플릿 - Phase 6 (시스템): pop-system (프로필, 테마, 대시보드 보이기/숨기기 통합) +### Phase 1 상세 변경 (2026-02-09 토의 결정) + +기존 계획에서 "KPI 카드 우선"이었으나, 토의 결과 **4개 서브타입 전부를 Phase 1에서 구현**으로 변경: +- kpi-card, chart, gauge, stat-card 모두 Phase 1 +- 멀티 아이템 컨테이너 (arrows, auto-slide, grid, scroll) +- 계산식 지원 (formula) +- 드롭다운 기반 쉬운 집계 설정 +- 기존 `frontend/components/pop/dashboard/` 폴더는 Phase 1 완료 후 폐기/삭제 + +### 백엔드 API 현황 (호환성 점검 완료) + +기존 백엔드에 이미 구현되어 있어 새로 만들 필요 없는 API: + +| API | 용도 | 비고 | +|-----|------|------| +| `dataApi.getTableData()` | 동적 테이블 조회 | 페이징, 검색, 정렬, 필터 | +| `dataApi.getJoinedData()` | 2개 테이블 조인 | Entity 조인, 필터링, 중복제거 | +| `entityJoinApi.getTableDataWithJoins()` | Entity 조인 전용 | ID->이름 자동 변환 | +| `dataApi.createRecord/updateRecord/deleteRecord()` | 동적 CRUD | - | +| `dataApi.upsertGroupedRecords()` | 그룹 UPSERT | - | +| `dashboardApi.executeQuery()` | SELECT SQL 직접 실행 | 집계/복합조인용 | +| `dashboardApi.getTableSchema()` | 테이블/컬럼 목록 | 설정 패널 드롭다운용 | + +**백엔드 신규 개발 불필요** - 기존 API만으로 모든 데이터 연동 가능 + +### useDataSource의 API 선택 전략 + +``` +단순 조회 (조인/집계 없음) -> dataApi.getTableData() 또는 entityJoinApi +2개 테이블 조인 -> dataApi.getJoinedData() +3개+ 테이블 조인 또는 집계 -> DataSourceConfig를 SQL로 변환 -> dashboardApi.executeQuery() +CRUD -> dataApi.createRecord/updateRecord/deleteRecord() +``` + +### POP 전용 훅 분리 (2026-02-09 결정) + +데스크탑과의 완전 분리를 위해 POP 전용 훅은 별도 폴더: +- `frontend/hooks/pop/usePopEvent.ts` (POP 전용) +- `frontend/hooks/pop/useDataSource.ts` (POP 전용) + +## 기존 시스템 호환성 검증 결과 (v8.0 추가) + +v8.0에서 추가된 모달 설계 방식에 대해 기존 시스템과의 호환성을 검증한 결과: + +### DB 스키마 (변경 불필요) + +| 테이블 | 현재 구조 | 호환성 | +|--------|-----------|--------| +| screen_layouts_v2 | layout_data JSONB + screen_id + company_code + layer_id | modalConfig를 컴포넌트 overrides에 포함하면 됨 | +| screen_layouts_pop | 동일 구조 (POP 전용) | 모달 화면도 별도 screen_id로 저장 가능 | + +- layout_data가 JSONB 타입이므로 어떤 JSON 구조든 저장 가능 +- 모달 화면을 별도 screen_id로 만들어도 기존 UNIQUE(screen_id, company_code, layer_id) 제약조건과 충돌 없음 +- DB 마이그레이션 불필요 + +### 백엔드 API (변경 불필요) + +| API | 엔드포인트 | 호환성 | +|-----|-----------|--------| +| POP 레이아웃 저장 | POST /api/screen-management/screens/:screenId/layout-pop | 모달 화면도 동일 API로 저장 | +| POP 레이아웃 조회 | GET /api/screen-management/screens/:screenId/layout-pop | 모달 화면도 동일 API로 조회 | +| 연결 모달 감지 | detectLinkedModals(screenId) | 화면 간 참조 관계 추적에 활용 | + +### 프론트엔드 (참고 패턴 존재) + +| 기존 기능 | 위치 | 활용 방안 | +|-----------|------|-----------| +| TabsWidget screenId 참조 | frontend/components/screen/widgets/TabsWidget.tsx | 같은 패턴으로 모달에서 외부 화면 로드 | +| TabsConfigPanel | frontend/components/screen/config-panels/TabsConfigPanel.tsx | pop-lookup 설정 패널의 모달 화면 선택 UI 참조 | +| ScreenDesigner 탭 내부 컴포넌트 | frontend/components/screen/ScreenDesigner.tsx | 모달 내부 컴포넌트 편집 패턴 참조 | + +### 결론 + +- DB 마이그레이션: 불필요 +- 백엔드 변경: 불필요 +- 프론트엔드: pop-lookup 컴포넌트 구현 시 기존 TabsWidget의 screenId 참조 패턴을 그대로 차용 +- 새로운 API: 불필요 (기존 saveLayoutPop/getLayoutPop로 충분) + ## 참고 파일 - 레지스트리: `frontend/lib/registry/PopComponentRegistry.ts` @@ -445,3 +691,6 @@ sequenceDiagram - 공통 스타일 타입: `frontend/lib/registry/pop-components/types.ts` - POP 타입 정의: `frontend/components/pop/designer/types/pop-layout.ts` - 기존 스펙 (v4): `popdocs/components-spec.md` +- 탭 위젯 (모달 참조 패턴): `frontend/components/screen/widgets/TabsWidget.tsx` +- POP 레이아웃 API: `frontend/lib/api/screen.ts` (saveLayoutPop, getLayoutPop) +- 백엔드 화면관리: `backend-node/src/controllers/screenManagementController.ts` diff --git a/frontend/app/(pop)/pop/screens/[screenId]/page.tsx b/frontend/app/(pop)/pop/screens/[screenId]/page.tsx index f578b30e..d17ced93 100644 --- a/frontend/app/(pop)/pop/screens/[screenId]/page.tsx +++ b/frontend/app/(pop)/pop/screens/[screenId]/page.tsx @@ -24,6 +24,8 @@ import { GRID_BREAKPOINTS, detectGridMode, } from "@/components/pop/designer/types/pop-layout"; +// POP 컴포넌트 자동 등록 (레지스트리 초기화 - PopRenderer보다 먼저 import) +import "@/lib/registry/pop-components"; import PopRenderer from "@/components/pop/designer/renderers/PopRenderer"; import { useResponsiveModeWithOverride, @@ -180,7 +182,7 @@ function PopScreenViewPage() { -
+
{/* 상단 툴바 (프리뷰 모드에서만) */} {isPreviewMode && (
@@ -261,7 +263,7 @@ function PopScreenViewPage() { )} {/* POP 화면 컨텐츠 */} -
+
{/* 현재 모드 표시 (일반 모드) */} {!isPreviewMode && (
@@ -270,7 +272,7 @@ function PopScreenViewPage() { )}
+ +
+ ); + } + + // 미등록 컴포넌트: 플레이스홀더 (fallback) + const typeLabel = COMPONENT_TYPE_LABELS[component.type] || component.type; return (
{component.label || typeLabel}