# POP 컴포넌트 정의서 v8.0 ## POP 헌법 (공통 규칙) ### 제1조. 컴포넌트의 정의 - 컴포넌트란 디자이너가 그리드에 배치하는 것이다 - 그리드에 배치하지 않는 것은 컴포넌트가 아니다 ### 제2조. 컴포넌트의 독립성 - 모든 컴포넌트는 독립적으로 동작한다 - 컴포넌트는 다른 컴포넌트의 존재를 직접 알지 못한다 (이벤트 버스로만 통신) ### 제3조. 데이터의 자유 - 모든 컴포넌트는 자신의 테이블 + 외부 테이블을 자유롭게 조인할 수 있다 - 컬럼별로 read/write/readwrite/hidden을 개별 설정할 수 있다 - 보유 데이터 중 원하는 컬럼만 골라서 저장할 수 있다 ### 제4조. 통신의 규칙 - 컴포넌트 간 통신은 반드시 이벤트 버스(usePopEvent)를 통한다 - 컴포넌트가 다른 컴포넌트를 직접 참조하거나 호출하지 않는다 - 이벤트는 화면 단위로 격리된다 (다른 POP 화면의 이벤트를 받지 않는다) - 같은 화면 안에서는 이벤트를 통해 자유롭게 데이터를 주고받을 수 있다 ### 제5조. 역할의 분리 - 조회용 입력(pop-search)과 저장용 입력(pop-field)은 다른 컴포넌트다 - 이동/실행(pop-icon)과 값 선택 후 반환(pop-lookup)은 다른 컴포넌트다 - 자주 쓰는 패턴은 하나로 합치되, 흐름 자체는 강제하고 보이는 방식만 옵션으로 제공한다 ### 제6조. 시스템 설정도 컴포넌트다 - 프로필, 테마, 대시보드 보이기/숨기기 같은 시스템 설정도 컴포넌트(pop-system)로 만든다 - 디자이너가 pop-system을 배치하지 않으면 해당 화면에 설정 기능이 없다 - 이를 통해 디자이너가 "이 화면에 설정 기능을 넣을지 말지"를 직접 결정한다 ### 제7조. 디자이너의 권한 - 디자이너는 컴포넌트를 배치하고 설정한다 - 디자이너는 사용자에게 커스텀을 허용할지 말지 결정한다 (userConfigurable) - 디자이너가 "사용자 커스텀 허용 = OFF"로 설정하면, 사용자는 변경할 수 없다 - 컴포넌트의 옵션 설정(어떻게 저장하고 어떻게 조회하는지 등)은 디자이너가 결정한다 ### 제8조. 컴포넌트의 구성 - 모든 컴포넌트는 3개 파일로 구성된다: 실제 컴포넌트, 디자인 미리보기, 설정 패널 - 모든 컴포넌트는 레지스트리에 등록해야 디자이너에 나타난다 - 모든 컴포넌트 인스턴스는 userConfigurable, displayName 공통 속성을 가진다 ### 제9조. 모달 화면의 설계 - 모달은 인라인(컴포넌트 설정만으로 구성)과 외부 참조(별도 POP 화면 연결) 두 가지 방식이 있다 - 단순한 목록 선택은 인라인 모달을 사용한다 (설정만으로 완결) - 복잡한 검색/필터가 필요하거나 여러 곳에서 재사용하는 모달은 별도 POP 화면을 만들어 참조한다 - 모달 안의 화면도 동일한 POP 컴포넌트 시스템으로 구성된다 (같은 그리드, 같은 컴포넌트) - 모달 화면의 layout_data는 기존 screen_layouts_pop 테이블에 저장한다 (DB 변경 불필요) --- ## 현재 상태 - 그리드 시스템 (v5.2): 완성 - 컴포넌트 레지스트리: 완성 (PopComponentRegistry.ts) - 구현 완료: `pop-text` 1개 (pop-text.tsx) - 기존 `components-spec.md`는 v4 기준이라 갱신 필요 ## 아키텍처 개요 ```mermaid graph TB subgraph designer [디자이너] Palette[컴포넌트 팔레트] Grid[CSS Grid 캔버스] ConfigPanel[속성 설정 패널] end subgraph registry [레지스트리] Registry[PopComponentRegistry] end subgraph infra [공통 인프라] DataSource[useDataSource 훅] EventBus[usePopEvent 훅] ActionRunner[usePopAction 훅] end subgraph components [9개 컴포넌트] Text[pop-text - 완성] Dashboard[pop-dashboard] Table[pop-table] Button[pop-button] Icon[pop-icon] Search[pop-search] Field[pop-field] Lookup[pop-lookup] System[pop-system] end subgraph backend [기존 백엔드 API] DataAPI[dataApi - 동적 CRUD] DashAPI[dashboardApi - 통계 쿼리] CodeAPI[commonCodeApi - 공통코드] NumberAPI[numberingRuleApi - 채번] end Palette --> Grid Grid --> ConfigPanel ConfigPanel --> Registry Registry --> components components --> infra infra --> backend EventBus -.->|컴포넌트 간 통신| components System -.->|보이기/숨기기 제어| components ``` --- ## 공통 인프라 (모든 컴포넌트가 공유) ### 핵심 원칙: 모든 컴포넌트는 데이터를 자유롭게 다룬다 1. **데이터 전달**: 모든 컴포넌트는 자신이 보유한 데이터를 다른 컴포넌트에 전달/수신 가능 2. **테이블 조인**: 자신의 테이블 + 외부 테이블 자유롭게 조인하여 데이터 구성 3. **컬럼별 CRUD 제어**: 컬럼 단위로 "조회만" / "저장 대상" / "숨김"을 개별 설정 가능 4. **선택적 저장**: 보유 데이터 중 원하는 컬럼만 골라서 저장/수정/삭제 가능 ### 공통 인스턴스 속성 (모든 컴포넌트 배치 시 설정 가능) 디자이너가 컴포넌트를 그리드에 배치할 때 설정하는 공통 속성: - `userConfigurable`: boolean - 사용자가 이 컴포넌트를 숨길 수 있는지 (개인 설정 패널에 노출) - `displayName`: string - 개인 설정 패널에 보여줄 이름 (예: "금일 생산실적") ### 1. DataSourceConfig (데이터 소스 설정 타입) 모든 데이터 연동 컴포넌트가 사용하는 표준 설정 구조: - `tableName`: 대상 테이블 - `columns`: 컬럼 바인딩 목록 (ColumnBinding 배열) - `filters`: 필터 조건 배열 - `sort`: 정렬 설정 - `aggregation`: 집계 함수 (count, sum, avg, min, max) - `joins`: 테이블 조인 설정 (JoinConfig 배열) - `refreshInterval`: 자동 새로고침 주기 (초) - `limit`: 조회 건수 제한 ### 1-1. ColumnBinding (컬럼별 읽기/쓰기 제어) 각 컬럼이 컴포넌트에서 어떤 역할을 하는지 개별 설정: - `columnName`: 컬럼명 - `sourceTable`: 소속 테이블 (조인된 외부 테이블 포함) - `mode`: "read" | "write" | "readwrite" | "hidden" - read: 조회만 (화면에 표시하되 저장 안 함) - write: 저장 대상 (사용자 입력 -> DB 저장) - readwrite: 조회 + 저장 모두 - hidden: 내부 참조용 (화면에 안 보이지만 다른 컴포넌트에 전달 가능) - `label`: 화면 표시 라벨 - `defaultValue`: 기본값 예시: 발주 품목 카드에서 5개 컬럼 중 3개만 저장 ``` columns: [ { columnName: "item_code", sourceTable: "order_items", mode: "read" }, { columnName: "item_name", sourceTable: "item_info", mode: "read" }, { columnName: "inbound_qty", sourceTable: "order_items", mode: "readwrite" }, { columnName: "warehouse", sourceTable: "order_items", mode: "write" }, { columnName: "memo", sourceTable: "order_items", mode: "write" }, ] ``` ### 1-2. JoinConfig (테이블 조인 설정) 외부 테이블과 자유롭게 조인: - `targetTable`: 조인할 외부 테이블명 - `joinType`: "inner" | "left" | "right" - `on`: 조인 조건 { sourceColumn, targetColumn } - `columns`: 가져올 컬럼 목록 ### 2. useDataSource 훅 DataSourceConfig를 받아서 기존 API를 호출하고 결과를 반환: - 로딩/에러/데이터 상태 관리 - 자동 새로고침 타이머 - 필터 변경 시 자동 재조회 - 기존 `dataApi`, `dashboardApi` 활용 - **CRUD 함수 제공**: save(data), update(id, data), delete(id) - ColumnBinding의 mode가 "write" 또는 "readwrite"인 컬럼만 저장 대상에 포함 - "read" 컬럼은 저장 시 자동 제외 ### 3. usePopEvent 훅 (이벤트 버스 - 데이터 전달 포함) 컴포넌트 간 통신 (단순 이벤트 + 데이터 페이로드): - `publish(eventName, payload)`: 이벤트 발행 - `subscribe(eventName, callback)`: 이벤트 구독 - `getSharedData(key)`: 공유 데이터 직접 읽기 - `setSharedData(key, value)`: 공유 데이터 직접 쓰기 - 화면 단위 스코프 (다른 POP 화면과 격리) ### 4. PopActionConfig (액션 설정 타입) 모든 컴포넌트가 사용할 수 있는 액션 표준 구조: - `type`: "navigate" | "modal" | "save" | "delete" | "api" | "event" | "refresh" - `navigate`: { screenId, url } - `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 } - `event`: { eventName, payload } - `refresh`: { targetComponents } --- ## 컴포넌트 정의 (9개) ### 1. pop-text (완성) - **한 줄 정의**: 보여주기만 함 - **카테고리**: display - **역할**: 정적 표시 전용 (이벤트 없음) - **서브타입**: text, datetime, image, title - **데이터**: 없음 (정적 콘텐츠) - **이벤트**: 발행 없음, 수신 없음 - **설정**: 내용, 폰트 크기/굵기, 좌우/상하 정렬, 이미지 URL/맞춤/크기, 날짜 포맷 빌더 ### 2. pop-dashboard (신규 - 2026-02-09 토의 결과 반영) - **한 줄 정의**: 여러 집계 아이템을 묶어서 다양한 방식으로 보여줌 - **카테고리**: display - **역할**: 숫자 데이터를 집계/계산하여 시각화. 하나의 컴포넌트 안에 여러 집계 아이템을 담는 컨테이너 - **구조**: 1개 pop-dashboard = 여러 DashboardItem의 묶음. 각 아이템은 독립적으로 데이터 소스/서브타입/보이기숨기기 설정 가능 - **서브타입** (아이템별로 선택, 한 묶음에 혼합 가능): - kpi-card: 숫자 + 단위 + 라벨 + 증감 표시 - chart: 막대/원형/라인 차트 - gauge: 게이지 (목표 대비 달성률) - stat-card: 통계 카드 (건수 + 대기 + 링크) - **표시 모드** (디자이너가 선택): - 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 (아이템 클릭 시 상세 데이터 전달) - **설정**: 데이터 소스(드롭다운 기반 쉬운 집계), 집계 함수, 계산식, 라벨, 단위, 색상 구간, 차트 타입, 새로고침 주기, 목표값, 표시 모드, 아이템별 보이기/숨기기 - **보이기/숨기기**: 각 아이템별로 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 (신규 - 가장 복잡) - **한 줄 정의**: 데이터 목록을 보여주고 편집함 - **카테고리**: display - **역할**: 데이터 목록 표시 + 편집 (카드형/테이블형) - **서브타입**: - card-list: 카드 형태 - table-list: 테이블 형태 (행/열 장부) - **데이터**: DataSourceConfig (조인/컬럼별 읽기쓰기 자유) - **CRUD**: useDataSource의 save/update/delete 사용. write/readwrite 컬럼만 자동 추출 - **카드 템플릿** (card-list 전용): 카드 내부 미니 그리드로 요소 배치, 요소별 데이터 바인딩 - **이벤트**: - 수신: filter_changed, refresh, data_ready - 발행: row_selected, row_action, save_complete, delete_complete - **설정**: 데이터 소스, 표시 모드, 카드 템플릿, 컬럼 정의, 행 선택 방식, 페이징, 정렬, 인라인 편집 여부 ### 4. pop-button (신규) - **한 줄 정의**: 누르면 액션 실행 (저장, 삭제 등) - **카테고리**: action - **역할**: 액션 실행 (저장, 삭제, API 호출, 모달 열기 등) - **데이터**: 이벤트로 수신한 데이터를 액션에 활용 - **CRUD**: 버튼 클릭 시 수신 데이터 기반으로 save/update/delete 실행 - **이벤트**: - 수신: data_ready, row_selected - 발행: save_complete, delete_complete 등 - **설정**: 라벨, 아이콘, 크기, 스타일, 액션 설정(PopActionConfig), 확인 다이얼로그, 로딩 상태 ### 5. pop-icon (신규) - **한 줄 정의**: 누르면 어딘가로 이동 (돌아오는 값 없음) - **카테고리**: action - **역할**: 네비게이션 (화면 이동, URL 이동, 새로고침) - **데이터**: 없음 - **이벤트**: 없음 (네비게이션은 이벤트가 아닌 직접 실행) - **설정**: 아이콘 종류(lucide-icon), 라벨, 배경색/그라디언트, 크기, 클릭 액션(PopActionConfig), 뱃지 표시 - **pop-lookup과의 차이**: pop-icon은 이동/실행만 함. 값을 선택해서 돌려주지 않음 ### 6. pop-search (신규) - **한 줄 정의**: 조건을 입력해서 다른 컴포넌트를 조회/필터링 - **카테고리**: input - **역할**: 다른 컴포넌트에 필터 조건 전달 + 자체 데이터 조회 - **서브타입**: - text-search: 텍스트 검색 - date-range: 날짜 범위 - select-filter: 드롭다운 선택 (공통코드 연동) - combo-filter: 복합 필터 (여러 조건 조합) - **실행 방식**: auto(값 변경 즉시) 또는 button(검색 버튼 클릭 시) - **데이터**: 공통코드/카테고리 API로 선택 항목 조회 - **이벤트**: - 수신: 없음 - 발행: filter_changed (필터 값 변경 시) - **설정**: 필터 타입, 대상 컬럼, 공통코드 연결, 플레이스홀더, 실행 방식(auto/button), 발행할 이벤트 이름 - **pop-field와의 차이**: pop-search 입력값은 조회용(DB에 안 들어감). pop-field 입력값은 저장용(DB에 들어감) ### 7. pop-field (신규) - **한 줄 정의**: 저장할 값을 입력 - **카테고리**: input - **역할**: 단일 데이터 입력 (폼 필드) - 입력한 값이 DB에 저장되는 것이 목적 - **서브타입**: - text: 텍스트 입력 - number: 숫자 입력 (수량, 금액) - date: 날짜 선택 - select: 드롭다운 선택 - numpad: 큰 숫자패드 (현장용) - **데이터**: DataSourceConfig (선택적) - select 옵션을 DB에서 조회 가능 - ColumnBinding으로 입력값의 저장 대상 테이블/컬럼 지정 - **CRUD**: 자체 저장은 보통 하지 않음. value_changed 이벤트로 pop-button 등에 전달 - **이벤트**: - 수신: set_value (외부에서 값 설정) - 발행: value_changed (값 + 컬럼명 + 모드 정보) - **설정**: 입력 타입, 라벨, 플레이스홀더, 필수 여부, 유효성 검증, 최소/최대값, 단위 표시, 바인딩 컬럼 ### 8. pop-lookup (신규) - **한 줄 정의**: 모달에서 값을 골라서 반환 - **카테고리**: input - **역할**: 필드를 클릭하면 모달이 열리고, 목록에서 선택하면 값이 반환되는 컴포넌트 - **서브타입 (모달 안 표시 방식)**: - card: 카드형 목록 - table: 테이블형 목록 - icon-grid: 아이콘 그리드 (참조 화면의 거래처 선택처럼) - **동작 흐름**: 필드 클릭 -> 모달 열림 -> 목록에서 선택 -> 모달 닫힘 -> 필드에 값 표시 + 이벤트 발행 - **데이터**: DataSourceConfig (모달 안 목록의 데이터 소스) - **이벤트**: - 수신: set_value (외부에서 값 초기화) - 발행: value_selected (선택한 레코드 전체 데이터 전달), filter_changed (선택 값을 필터로 전달) - **설정**: 라벨, 플레이스홀더, 데이터 소스, 모달 표시 방식(card/table/icon-grid), 표시 컬럼(모달 목록에 보여줄 컬럼), 반환 컬럼(선택 시 돌려줄 값), 발행할 이벤트 이름 - **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 (신규) - **한 줄 정의**: 시스템 설정을 하나로 통합한 컴포넌트 (프로필, 테마, 보이기/숨기기) - **카테고리**: system - **역할**: 사용자 개인 설정 기능을 제공하는 통합 컴포넌트 - **내부 포함 기능**: - 프로필 표시 (사용자명, 부서) - 테마 선택 (기본/다크/블루/그린) - 대시보드 보이기/숨기기 체크박스 (같은 화면의 userConfigurable=true 컴포넌트를 자동 수집) - 하단 메뉴 보이기/숨기기 - 드래그앤드롭으로 순서 변경 - **디자이너가 설정하는 것**: 크기(그리드에서 차지하는 영역), 내부 라벨/아이콘 크기와 위치 - **사용자가 하는 것**: 체크박스로 컴포넌트 보이기/숨기기, 테마 선택, 순서 변경 - **데이터**: 같은 화면의 layout_data에서 컴포넌트 목록을 자동 수집 - **저장**: 사용자별 설정을 localStorage에 저장 (데스크탑 패턴 따름) - **이벤트**: - 수신: 없음 - 발행: visibility_changed (컴포넌트 보이기/숨기기 변경 시), theme_changed (테마 변경 시) - **설정**: 내부 라벨 크기, 아이콘 크기, 위치 정도만 - **특이사항**: - 디자이너가 이 컴포넌트를 배치하지 않으면 해당 화면에 개인 설정 기능이 없다 - 디자이너가 "이 화면에 설정 기능을 넣을지 말지"를 직접 결정하는 구조 - 메인 홈에는 배치, 업무 화면(입고 등)에는 안 배치하는 식으로 사용 --- ## 컴포넌트 간 통신 예시 ### 예시 1: 검색 -> 필터 연동 ```mermaid sequenceDiagram participant Search as pop-search participant Dashboard as pop-dashboard participant Table as pop-table Note over Search: 사용자가 창고 WH01 선택 Search->>Dashboard: filter_changed Search->>Table: filter_changed Note over Dashboard: DataSource 재조회 Note over Table: DataSource 재조회 ``` ### 예시 2: 데이터 전달 + 선택적 저장 ```mermaid sequenceDiagram participant Table as pop-table participant Field as pop-field participant Button as pop-button Note over Table: 사용자가 발주 행 선택 Table->>Field: row_selected Table->>Button: row_selected Note over Field: 사용자가 qty를 500으로 입력 Field->>Button: value_changed Note over Button: 사용자가 저장 클릭 Note over Button: write/readwrite 컬럼만 추출하여 저장 Button->>Table: save_complete Note over Table: 데이터 새로고침 ``` ### 예시 3: pop-lookup 거래처 선택 -> 품목 조회 ```mermaid sequenceDiagram participant Lookup as pop-lookup participant Table as pop-table Note over Lookup: 사용자가 거래처 필드 클릭 Note over Lookup: 모달 열림 - 거래처 목록 표시 Note over Lookup: 사용자가 대한금속 선택 Note over Lookup: 모달 닫힘 - 필드에 대한금속 표시 Lookup->>Table: filter_changed { company: "대한금속" } Note over Table: company=대한금속 필터로 재조회 Note over Table: 발주 품목 3건 표시 ``` ### 예시 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개 컬럼이 있는 발주 화면: - item_code (read) -> 화면에 표시, 저장 안 함 - item_name (read, 조인) -> item_info 테이블에서 가져옴, 저장 안 함 - inbound_qty (readwrite) -> 화면에 표시 + 사용자 수정 + 저장 - warehouse (write) -> 사용자 입력 + 저장 - memo (write) -> 사용자 입력 + 저장 저장 API 호출 시: `{ inbound_qty: 500, warehouse: "WH01", memo: "긴급" }` 만 전달 조회 API 호출 시: 5개 컬럼 전부 + 조인된 item_name까지 조회 --- ## 구현 우선순위 - Phase 0 (공통 인프라): ColumnBinding, JoinConfig, DataSourceConfig 타입, useDataSource 훅 (CRUD 포함), usePopEvent 훅 (데이터 전달 포함), PopActionConfig 타입 - 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 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` - 기존 텍스트 컴포넌트: `frontend/lib/registry/pop-components/pop-text.tsx` - 공통 스타일 타입: `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`