- pop-icon.tsx 신규 추가: 아이콘 컴포넌트 구현 - ComponentPalette: 아이콘 컴포넌트 팔레트 추가 - ComponentEditorPanel: 아이콘 편집 패널 추가 - PopRenderer: 아이콘 렌더링 지원 - pop-layout.ts: 아이콘 타입 정의 추가 - pop-text.tsx: 텍스트 컴포넌트 개선 - next.config.mjs: 설정 업데이트 Co-authored-by: Cursor <cursoragent@cursor.com>
31 KiB
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-text1개 (pop-text.tsx) - 기존
components-spec.md는 v4 기준이라 갱신 필요
아키텍처 개요
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
공통 인프라 (모든 컴포넌트가 공유)
핵심 원칙: 모든 컴포넌트는 데이터를 자유롭게 다룬다
- 데이터 전달: 모든 컴포넌트는 자신이 보유한 데이터를 다른 컴포넌트에 전달/수신 가능
- 테이블 조인: 자신의 테이블 + 외부 테이블 자유롭게 조인하여 데이터 구성
- 컬럼별 CRUD 제어: 컬럼 단위로 "조회만" / "저장 대상" / "숨김"을 개별 설정 가능
- 선택적 저장: 보유 데이터 중 원하는 컬럼만 골라서 저장/수정/삭제 가능
공통 인스턴스 속성 (모든 컴포넌트 배치 시 설정 가능)
디자이너가 컴포넌트를 그리드에 배치할 때 설정하는 공통 속성:
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: 검색 -> 필터 연동
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: 데이터 전달 + 선택적 저장
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 거래처 선택 -> 품목 조회
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 외부 화면 참조
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