Compare commits
1 Commits
main
...
mhkim-node
| Author | SHA1 | Date |
|---|---|---|
|
|
5e8572954a |
|
|
@ -1,38 +0,0 @@
|
||||||
---
|
|
||||||
description: 컴포넌트 추가/수정 또는 DB 구조 변경 시 관련 문서를 항상 최신화하도록 강제하는 규칙
|
|
||||||
globs:
|
|
||||||
- "frontend/lib/registry/components/**/*.tsx"
|
|
||||||
- "frontend/components/v2/**/*.tsx"
|
|
||||||
- "db/migrations/**/*.sql"
|
|
||||||
- "backend-node/src/types/ddl.ts"
|
|
||||||
---
|
|
||||||
|
|
||||||
# 컴포넌트 및 DB 구조 변경 시 문서 동기화 규칙
|
|
||||||
|
|
||||||
## 🚨 핵심 원칙 (절대 준수)
|
|
||||||
|
|
||||||
새로운 V2 컴포넌트를 생성하거나 기존 컴포넌트의 설정(overrides)을 변경할 때, 또는 DB 테이블 구조나 화면 생성 파이프라인이 변경될 때는 **반드시** 아래 두 문서를 함께 업데이트해야 합니다.
|
|
||||||
|
|
||||||
1. `docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md` (전체 레퍼런스)
|
|
||||||
2. `docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md` (실행 가이드)
|
|
||||||
|
|
||||||
## 📌 업데이트 대상 및 방법
|
|
||||||
|
|
||||||
### 1. V2 컴포넌트 신규 추가 또는 속성(Props/Overrides) 변경 시
|
|
||||||
- **`full-screen-analysis.md`**: `3. 컴포넌트 전체 설정 레퍼런스` 섹션에 해당 컴포넌트의 모든 설정값(타입, 기본값, 설명)을 표 형태로 추가/수정하세요.
|
|
||||||
- **`v2-component-usage-guide.md`**:
|
|
||||||
- `7. Step 6: screen_layouts_v2 INSERT`의 컴포넌트 url 매핑표에 추가하세요.
|
|
||||||
- `16. 컴포넌트 빠른 참조표`에 추가하세요.
|
|
||||||
- 필요한 경우 `8. 패턴별 layout_data 완전 예시`에 새로운 패턴을 추가하세요.
|
|
||||||
|
|
||||||
### 2. DB 테이블 구조 또는 화면 생성 로직 변경 시
|
|
||||||
- **`full-screen-analysis.md`**: `2. DB 테이블 스키마` 섹션의 테이블 구조(컬럼, 타입, 설명)를 최신화하세요.
|
|
||||||
- **`v2-component-usage-guide.md`**:
|
|
||||||
- `Step 1` ~ `Step 7`의 SQL 템플릿이 변경된 구조와 일치하는지 확인하고 수정하세요.
|
|
||||||
- 특히 `INSERT` 문의 컬럼 목록과 `VALUES` 형식이 정확한지 검증하세요.
|
|
||||||
|
|
||||||
## ⚠️ AI 에이전트 행동 지침
|
|
||||||
|
|
||||||
1. 사용자가 컴포넌트 코드를 수정해달라고 요청하면, 수정 완료 후 **"관련 가이드 문서도 업데이트할까요?"** 라고 반드시 물어보세요.
|
|
||||||
2. 사용자가 DB 마이그레이션 스크립트를 작성해달라고 하거나 핵심 시스템 테이블을 건드리면, 가이드 문서의 SQL 템플릿도 수정해야 하는지 확인하세요.
|
|
||||||
3. 가이드 문서 업데이트 시 JSON 예제 안에 `//` 같은 주석을 넣지 않도록 주의하세요 (DB 파싱 에러 방지).
|
|
||||||
|
|
@ -153,6 +153,7 @@ backend-node/uploads/
|
||||||
uploads/
|
uploads/
|
||||||
*.jpg
|
*.jpg
|
||||||
*.jpeg
|
*.jpeg
|
||||||
|
*.png
|
||||||
*.gif
|
*.gif
|
||||||
*.pdf
|
*.pdf
|
||||||
*.doc
|
*.doc
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 329 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 342 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
|
|
@ -0,0 +1,331 @@
|
||||||
|
# 화면 전체 분석 보고서
|
||||||
|
|
||||||
|
> **분석 대상**: `/Users/kimjuseok/Downloads/화면개발 8` 폴더 내 핵심 업무 화면
|
||||||
|
> **분석 기준**: 메뉴별 분류, 3개 이상 재활용 가능한 컴포넌트 식별
|
||||||
|
> **분석 일자**: 2026-01-30
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 현재 사용 중인 V2 컴포넌트 목록
|
||||||
|
|
||||||
|
> **중요**: v2- 접두사가 붙은 컴포넌트만 사용합니다.
|
||||||
|
|
||||||
|
### 입력 컴포넌트
|
||||||
|
| ID | 이름 | 용도 |
|
||||||
|
|----|------|------|
|
||||||
|
| `v2-input` | V2 입력 | 텍스트, 숫자, 비밀번호, 이메일 등 입력 |
|
||||||
|
| `v2-select` | V2 선택 | 드롭다운, 콤보박스, 라디오, 체크박스 |
|
||||||
|
| `v2-date` | V2 날짜 | 날짜, 시간, 날짜범위 입력 |
|
||||||
|
|
||||||
|
### 표시 컴포넌트
|
||||||
|
| ID | 이름 | 용도 |
|
||||||
|
|----|------|------|
|
||||||
|
| `v2-text-display` | 텍스트 표시 | 라벨, 텍스트 표시 |
|
||||||
|
| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 |
|
||||||
|
| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수 등 집계 표시 |
|
||||||
|
|
||||||
|
### 테이블/데이터 컴포넌트
|
||||||
|
| ID | 이름 | 용도 |
|
||||||
|
|----|------|------|
|
||||||
|
| `v2-table-list` | 테이블 리스트 | 데이터 테이블 표시, 페이지네이션, 정렬, 필터 |
|
||||||
|
| `v2-table-search-widget` | 검색 필터 | 화면 내 테이블 검색/필터/그룹 기능 |
|
||||||
|
| `v2-pivot-grid` | 피벗 그리드 | 다차원 데이터 분석 (피벗 테이블) |
|
||||||
|
|
||||||
|
### 레이아웃 컴포넌트
|
||||||
|
| ID | 이름 | 용도 |
|
||||||
|
|----|------|------|
|
||||||
|
| `v2-split-panel-layout` | 분할 패널 | 마스터-디테일 좌우 분할 레이아웃 |
|
||||||
|
| `v2-tabs-widget` | 탭 위젯 | 탭 전환, 탭 내 컴포넌트 배치 |
|
||||||
|
| `v2-section-card` | Section Card | 제목/테두리가 있는 그룹화 컨테이너 |
|
||||||
|
| `v2-section-paper` | Section Paper | 배경색 기반 미니멀 그룹화 컨테이너 |
|
||||||
|
| `v2-divider-line` | 구분선 | 영역 구분 |
|
||||||
|
| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 내부 컴포넌트 반복 렌더링 |
|
||||||
|
| `v2-repeater` | 리피터 | 반복 컨트롤 |
|
||||||
|
|
||||||
|
### 액션/기타 컴포넌트
|
||||||
|
| ID | 이름 | 용도 |
|
||||||
|
|----|------|------|
|
||||||
|
| `v2-button-primary` | 기본 버튼 | 저장, 삭제 등 액션 버튼 |
|
||||||
|
| `v2-numbering-rule` | 채번규칙 | 자동 코드/번호 생성 |
|
||||||
|
| `v2-category-manager` | 카테고리 관리자 | 카테고리 관리 |
|
||||||
|
| `v2-location-swap-selector` | 위치 교환 선택기 | 위치 교환 기능 |
|
||||||
|
| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 |
|
||||||
|
| `v2-media` | 미디어 | 미디어 표시 |
|
||||||
|
|
||||||
|
**총 23개 V2 컴포넌트**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 화면 분류 (메뉴별)
|
||||||
|
|
||||||
|
### 01. 기준정보 (master-data)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 회사정보 | 회사정보.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 부서정보 | 부서정보.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 품목정보 | 품목정보.html | 검색+테이블+그룹화 | ⚠️ 그룹화 미지원 |
|
||||||
|
| BOM관리 | BOM관리.html | 분할패널+트리 | ⚠️ 트리뷰 미지원 |
|
||||||
|
| 공정정보관리 | 공정정보관리.html | 분할패널+테이블 | ✅ 완전 |
|
||||||
|
| 공정작업기준 | 공정작업기준관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 품목라우팅 | 품목라우팅관리.html | 분할패널+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
### 02. 영업관리 (sales)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 수주관리 | 수주관리.html | 분할패널+테이블 | ✅ 완전 |
|
||||||
|
| 견적관리 | 견적관리.html | 분할패널+테이블 | ✅ 완전 |
|
||||||
|
| 거래처관리 | 거래처관리.html | 분할패널+탭+그룹화 | ⚠️ 그룹화 미지원 |
|
||||||
|
| 판매품목정보 | 판매품목정보.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 출하계획관리 | 출하계획관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
### 03. 생산관리 (production)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 생산계획관리 | 생산계획관리.html | 분할패널+탭+타임라인 | ❌ 타임라인 미지원 |
|
||||||
|
| 생산관리 | 생산관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 생산실적관리 | 생산실적관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 작업지시 | 작업지시.html | 탭+그룹화테이블+분할패널 | ⚠️ 그룹화 미지원 |
|
||||||
|
| 공정관리 | 공정관리.html | 분할패널+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
### 04. 구매관리 (purchase)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 발주관리 | 발주관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 공급업체관리 | 공급업체관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 구매입고 | pages/구매입고.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
### 05. 설비관리 (equipment)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 설비정보 | 설비정보.html | 분할패널+카드+탭 | ✅ v2-card-display 활용 |
|
||||||
|
|
||||||
|
### 06. 물류관리 (logistics)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 창고관리 | 창고관리.html | 모바일앱스타일+iframe | ❌ 별도개발 필요 |
|
||||||
|
| 창고정보관리 | 창고정보관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 입출고관리 | 입출고관리.html | 검색+테이블+그룹화 | ⚠️ 그룹화 미지원 |
|
||||||
|
| 재고현황 | 재고현황.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
### 07. 품질관리 (quality)
|
||||||
|
| 화면명 | 파일명 | 패턴 | 구현 가능 |
|
||||||
|
|--------|--------|------|----------|
|
||||||
|
| 검사기준 | 검사기준.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 검사정보관리 | 검사정보관리.html | 탭+테이블 | ✅ 완전 |
|
||||||
|
| 검사장비관리 | 검사장비관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 불량관리 | 불량관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
| 클레임관리 | 클레임관리.html | 검색+테이블 | ✅ 완전 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 UI 패턴 분석
|
||||||
|
|
||||||
|
### 패턴 A: 검색 + 테이블 (가장 기본)
|
||||||
|
**해당 화면**: 약 60% (15개 이상)
|
||||||
|
|
||||||
|
**사용 컴포넌트**:
|
||||||
|
- `v2-table-search-widget`: 검색 필터
|
||||||
|
- `v2-table-list`: 데이터 테이블
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ [검색필드들...] [조회] [엑셀] │ ← v2-table-search-widget
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ 테이블 제목 [신규등록] [삭제] │
|
||||||
|
│ ────────────────────────────────────── │
|
||||||
|
│ □ | 코드 | 이름 | 상태 | 등록일 | │ ← v2-table-list
|
||||||
|
│ □ | A001 | 테스트| 사용 | 2026-01-30 | │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 패턴 B: 분할 패널 (마스터-디테일)
|
||||||
|
**해당 화면**: 약 25% (8개)
|
||||||
|
|
||||||
|
**사용 컴포넌트**:
|
||||||
|
- `v2-split-panel-layout`: 좌우 분할
|
||||||
|
- `v2-table-list`: 마스터/디테일 테이블
|
||||||
|
- `v2-tabs-widget`: 상세 탭 (선택)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┬──────────────────────┐
|
||||||
|
│ 마스터 리스트 │ 상세 정보 / 탭 │
|
||||||
|
│ ─────────────── │ ┌────┬────┬────┐ │
|
||||||
|
│ □ A001 제품A │ │기본│이력│첨부│ │
|
||||||
|
│ □ A002 제품B ← │ └────┴────┴────┘ │
|
||||||
|
│ □ A003 제품C │ [테이블 or 폼] │
|
||||||
|
└──────────────────┴──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 패턴 C: 탭 + 테이블
|
||||||
|
**해당 화면**: 약 10% (3개)
|
||||||
|
|
||||||
|
**사용 컴포넌트**:
|
||||||
|
- `v2-tabs-widget`: 탭 전환
|
||||||
|
- `v2-table-list`: 탭별 테이블
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ [탭1] [탭2] [탭3] │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ [테이블 영역] │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 패턴 D: 특수 UI
|
||||||
|
**해당 화면**: 약 5% (2개)
|
||||||
|
|
||||||
|
- 생산계획관리: 타임라인/간트 차트 → **v2-timeline 미존재**
|
||||||
|
- 창고관리: 모바일 앱 스타일 → **별도 개발 필요**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 신규 컴포넌트 분석 (3개 이상 재활용 기준)
|
||||||
|
|
||||||
|
### 4.1 v2-grouped-table (그룹화 테이블)
|
||||||
|
**재활용 화면 수**: 5개 이상 ✅
|
||||||
|
|
||||||
|
| 화면 | 그룹화 기준 |
|
||||||
|
|------|------------|
|
||||||
|
| 품목정보 | 품목구분, 카테고리 |
|
||||||
|
| 거래처관리 | 거래처유형, 지역 |
|
||||||
|
| 작업지시 | 작업일자, 공정 |
|
||||||
|
| 입출고관리 | 입출고구분, 창고 |
|
||||||
|
| 견적관리 | 상태, 거래처 |
|
||||||
|
|
||||||
|
**기능 요구사항**:
|
||||||
|
- 특정 컬럼 기준 그룹핑
|
||||||
|
- 그룹 접기/펼치기
|
||||||
|
- 그룹 헤더에 집계 표시
|
||||||
|
- 다중 그룹핑 지원
|
||||||
|
|
||||||
|
**구현 복잡도**: 중
|
||||||
|
|
||||||
|
### 4.2 v2-tree-view (트리 뷰)
|
||||||
|
**재활용 화면 수**: 3개 ✅
|
||||||
|
|
||||||
|
| 화면 | 트리 용도 |
|
||||||
|
|------|----------|
|
||||||
|
| BOM관리 | BOM 구조 (정전개/역전개) |
|
||||||
|
| 부서정보 | 조직도 |
|
||||||
|
| 메뉴관리 | 메뉴 계층 |
|
||||||
|
|
||||||
|
**기능 요구사항**:
|
||||||
|
- 노드 접기/펼치기
|
||||||
|
- 드래그앤드롭 (선택)
|
||||||
|
- 정전개/역전개 전환
|
||||||
|
- 노드 선택 이벤트
|
||||||
|
|
||||||
|
**구현 복잡도**: 중상
|
||||||
|
|
||||||
|
### 4.3 v2-timeline-scheduler (타임라인)
|
||||||
|
**재활용 화면 수**: 1~2개 (기준 미달)
|
||||||
|
|
||||||
|
| 화면 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| 생산계획관리 | 간트 차트 |
|
||||||
|
| 설비 가동 현황 | 타임라인 |
|
||||||
|
|
||||||
|
**기능 요구사항**:
|
||||||
|
- 시간축 기반 배치
|
||||||
|
- 드래그로 일정 변경
|
||||||
|
- 공정별 색상 구분
|
||||||
|
- 줌 인/아웃
|
||||||
|
|
||||||
|
**구현 복잡도**: 상
|
||||||
|
|
||||||
|
> **참고**: 3개 미만이므로 우선순위 하향
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 컴포넌트 커버리지
|
||||||
|
|
||||||
|
### 현재 V2 컴포넌트로 구현 가능
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ 17개 화면 (65%) │
|
||||||
|
│ - 기본 검색 + 테이블 패턴 │
|
||||||
|
│ - 분할 패널 │
|
||||||
|
│ - 탭 전환 │
|
||||||
|
│ - 카드 디스플레이 │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### v2-grouped-table 개발 후
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ +5개 화면 (22개, 85%) │
|
||||||
|
│ - 품목정보, 거래처관리, 작업지시 │
|
||||||
|
│ - 입출고관리, 견적관리 │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### v2-tree-view 개발 후
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ +2개 화면 (24개, 92%) │
|
||||||
|
│ - BOM관리, 부서정보(계층) │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 별도 개발 필요
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ 2개 화면 (8%) │
|
||||||
|
│ - 생산계획관리 (타임라인) │
|
||||||
|
│ - 창고관리 (모바일 앱 스타일) │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 신규 컴포넌트 개발 우선순위
|
||||||
|
|
||||||
|
| 순위 | 컴포넌트 | 재활용 화면 수 | 복잡도 | ROI |
|
||||||
|
|------|----------|--------------|--------|-----|
|
||||||
|
| 1 | v2-grouped-table | 5+ | 중 | ⭐⭐⭐⭐⭐ |
|
||||||
|
| 2 | v2-tree-view | 3 | 중상 | ⭐⭐⭐⭐ |
|
||||||
|
| 3 | v2-timeline-scheduler | 1~2 | 상 | ⭐⭐ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 권장 구현 전략
|
||||||
|
|
||||||
|
### Phase 1: 즉시 구현 (현재 V2 컴포넌트)
|
||||||
|
- 회사정보, 부서정보
|
||||||
|
- 발주관리, 공급업체관리
|
||||||
|
- 검사기준, 검사장비관리, 불량관리
|
||||||
|
- 창고정보관리, 재고현황
|
||||||
|
- 공정작업기준관리
|
||||||
|
- 수주관리, 견적관리, 공정관리
|
||||||
|
- 설비정보 (v2-card-display 활용)
|
||||||
|
- 검사정보관리
|
||||||
|
|
||||||
|
### Phase 2: v2-grouped-table 개발 후
|
||||||
|
- 품목정보, 거래처관리, 입출고관리
|
||||||
|
- 작업지시
|
||||||
|
|
||||||
|
### Phase 3: v2-tree-view 개발 후
|
||||||
|
- BOM관리
|
||||||
|
- 부서정보 (계층 뷰)
|
||||||
|
|
||||||
|
### Phase 4: 개별 개발
|
||||||
|
- 생산계획관리 (타임라인)
|
||||||
|
- 창고관리 (모바일 스타일)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 요약
|
||||||
|
|
||||||
|
| 항목 | 수치 |
|
||||||
|
|------|------|
|
||||||
|
| 전체 분석 화면 수 | 26개 |
|
||||||
|
| 현재 즉시 구현 가능 | 17개 (65%) |
|
||||||
|
| v2-grouped-table 추가 시 | 22개 (85%) |
|
||||||
|
| v2-tree-view 추가 시 | 24개 (92%) |
|
||||||
|
| 별도 개발 필요 | 2개 (8%) |
|
||||||
|
|
||||||
|
**핵심 결론**:
|
||||||
|
1. **현재 V2 컴포넌트**로 65% 화면 구현 가능
|
||||||
|
2. **v2-grouped-table** 1개 컴포넌트 개발로 85%까지 확대
|
||||||
|
3. **v2-tree-view** 추가로 92% 도달
|
||||||
|
4. 나머지 8%는 화면별 특수 UI (타임라인, 모바일 스타일)로 개별 개발 필요
|
||||||
|
|
@ -0,0 +1,631 @@
|
||||||
|
# V2 공통 컴포넌트 사용 가이드
|
||||||
|
|
||||||
|
> **목적**: 다양한 회사에서 V2 컴포넌트를 활용하여 화면을 개발할 때 참고하는 범용 가이드
|
||||||
|
> **대상**: 화면 설계자, 개발자
|
||||||
|
> **버전**: 1.1.0
|
||||||
|
> **작성일**: 2026-02-23 (최종 업데이트)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. V2 컴포넌트로 가능한 것 / 불가능한 것
|
||||||
|
|
||||||
|
### 1.1 가능한 화면 유형
|
||||||
|
|
||||||
|
| 화면 유형 | 설명 | 대표 예시 |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| 마스터 관리 | 단일 테이블 CRUD | 회사정보, 부서정보, 코드관리 |
|
||||||
|
| 마스터-디테일 | 좌측 선택 → 우측 상세 | 공정관리, 품목라우팅, 견적관리 |
|
||||||
|
| 탭 기반 화면 | 탭별 다른 테이블/뷰 | 검사정보관리, 거래처관리 |
|
||||||
|
| 카드 뷰 | 이미지+정보 카드 형태 | 설비정보, 대시보드 |
|
||||||
|
| 피벗 분석 | 다차원 집계 | 매출분석, 재고현황 |
|
||||||
|
| 반복 컨테이너 | 데이터 수만큼 UI 반복 | 주문 상세, 항목 리스트 |
|
||||||
|
| 그룹화 테이블 | 그룹핑 기능 포함 테이블 | 카테고리별 집계, 부서별 현황 |
|
||||||
|
| 타임라인/스케줄 | 시간축 기반 일정 관리 | 생산일정, 작업스케줄 |
|
||||||
|
|
||||||
|
### 1.2 불가능한 화면 유형 (별도 개발 필요)
|
||||||
|
|
||||||
|
| 화면 유형 | 이유 | 해결 방안 |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| 트리 뷰 (계층 구조) | 트리 컴포넌트 미존재 | `v2-tree-view` 개발 필요 |
|
||||||
|
| 드래그앤드롭 보드 | 칸반 스타일 UI 없음 | 별도 개발 |
|
||||||
|
| 모바일 앱 스타일 | 네이티브 앱 UI | 별도 개발 |
|
||||||
|
| 복잡한 차트 | 기본 집계 외 시각화 | 차트 라이브러리 연동 |
|
||||||
|
|
||||||
|
> **참고**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)는 v1.1에서 추가되어 이제 지원됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. V2 컴포넌트 전체 목록 (25개)
|
||||||
|
|
||||||
|
### 2.1 입력 컴포넌트 (4개)
|
||||||
|
|
||||||
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|
|----|------|------|----------|
|
||||||
|
| `v2-input` | 입력 | 텍스트, 숫자, 비밀번호, 슬라이더, 컬러 | inputType(text/number/password/slider/color/button), format(email/tel/url/currency/biz_no), required, readonly, maxLength, min, max, step |
|
||||||
|
| `v2-select` | 선택 | 드롭다운, 콤보박스, 라디오, 체크, 태그, 토글, 스왑 | mode(dropdown/combobox/radio/check/tag/tagbox/toggle/swap), source(static/code/db/api/entity/category/distinct/select), searchable, multiple, cascading |
|
||||||
|
| `v2-date` | 날짜 | 날짜, 시간, 날짜시간 | dateType(date/time/datetime), format, range, minDate, maxDate, showToday |
|
||||||
|
| `v2-file-upload` | 파일 업로드 | 파일/이미지 업로드 | - |
|
||||||
|
|
||||||
|
### 2.2 표시 컴포넌트 (3개)
|
||||||
|
|
||||||
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|
|----|------|------|----------|
|
||||||
|
| `v2-text-display` | 텍스트 표시 | 라벨, 제목, 설명 텍스트 | fontSize, fontWeight, color, textAlign |
|
||||||
|
| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, cardSpacing, columnMapping(titleColumn/subtitleColumn/descriptionColumn/imageColumn), cardStyle(imagePosition/imageSize), dataSource(table/static/api) |
|
||||||
|
| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수, 최대, 최소 | items, filters, layout |
|
||||||
|
|
||||||
|
### 2.3 테이블/데이터 컴포넌트 (4개)
|
||||||
|
|
||||||
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|
|----|------|------|----------|
|
||||||
|
| `v2-table-list` | 테이블 리스트 | 데이터 조회/편집 테이블 | selectedTable, columns, pagination, filter, displayMode(table/card), checkbox, horizontalScroll, linkedFilters, excludeFilter, toolbar, tableStyle, autoLoad |
|
||||||
|
| `v2-table-search-widget` | 검색 필터 | 테이블 검색/필터/그룹 | autoSelectFirstTable, showTableSelector, title |
|
||||||
|
| `v2-pivot-grid` | 피벗 그리드 | 다차원 분석 (행/열/데이터 영역) | fields(area: row/column/data/filter, summaryType: sum/avg/count/min/max/countDistinct, groupInterval: year/quarter/month/week/day), dataSource(type: table/api/static, joinConfigs, filterConditions) |
|
||||||
|
| `v2-table-grouped` | 그룹화 테이블 | 그룹핑 기능이 포함된 테이블 | - |
|
||||||
|
|
||||||
|
### 2.4 레이아웃 컴포넌트 (7개)
|
||||||
|
|
||||||
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|
|----|------|------|----------|
|
||||||
|
| `v2-split-panel-layout` | 분할 패널 | 마스터-디테일 좌우 분할 | splitRatio, resizable, minLeftWidth, minRightWidth, syncSelection, panel별: displayMode(list/table/custom), relation(type/foreignKey), editButton, addButton, deleteButton, additionalTabs |
|
||||||
|
| `v2-tabs-widget` | 탭 위젯 | 탭 전환, 탭 내 컴포넌트 | tabs(id/label/order/disabled/components), defaultTab, orientation(horizontal/vertical), allowCloseable, persistSelection |
|
||||||
|
| `v2-section-card` | 섹션 카드 | 제목+테두리 그룹화 | title, collapsible, padding |
|
||||||
|
| `v2-section-paper` | 섹션 페이퍼 | 배경색 그룹화 | backgroundColor, padding, shadow |
|
||||||
|
| `v2-divider-line` | 구분선 | 영역 구분 | orientation, thickness |
|
||||||
|
| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 반복 렌더링 | dataSourceType, layout, gridColumns |
|
||||||
|
| `v2-repeater` | 리피터 | 반복 컨트롤 (inline/modal) | - |
|
||||||
|
|
||||||
|
### 2.5 액션/특수 컴포넌트 (7개)
|
||||||
|
|
||||||
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|
|----|------|------|----------|
|
||||||
|
| `v2-button-primary` | 기본 버튼 | 저장, 삭제 등 액션 | text, actionType, variant |
|
||||||
|
| `v2-numbering-rule` | 채번규칙 | 자동 코드/번호 생성 | rule, prefix, format |
|
||||||
|
| `v2-category-manager` | 카테고리 관리자 | 카테고리 관리 UI | - |
|
||||||
|
| `v2-location-swap-selector` | 위치 교환 | 위치 선택/교환 | - |
|
||||||
|
| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 | - |
|
||||||
|
| `v2-media` | 미디어 | 이미지/동영상 표시 | - |
|
||||||
|
| `v2-timeline-scheduler` | 타임라인 스케줄러 | 시간축 기반 일정/작업 관리 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 패턴별 컴포넌트 조합
|
||||||
|
|
||||||
|
### 3.1 패턴 A: 기본 마스터 화면 (가장 흔함)
|
||||||
|
|
||||||
|
**적용 화면**: 코드관리, 사용자관리, 부서정보, 창고정보 등
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ v2-table-search-widget │
|
||||||
|
│ [검색필드1] [검색필드2] [조회] [엑셀] │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ v2-table-list │
|
||||||
|
│ 제목 [신규] [삭제] │
|
||||||
|
│ ─────────────────────────────────────────────── │
|
||||||
|
│ □ | 코드 | 이름 | 상태 | 등록일 | │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**필수 컴포넌트**:
|
||||||
|
- `v2-table-search-widget` (1개)
|
||||||
|
- `v2-table-list` (1개)
|
||||||
|
|
||||||
|
**설정 포인트**:
|
||||||
|
- 테이블명 지정
|
||||||
|
- 검색 대상 컬럼 설정
|
||||||
|
- 컬럼 표시/숨김 설정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 패턴 B: 마스터-디테일 화면
|
||||||
|
|
||||||
|
**적용 화면**: 공정관리, 견적관리, 수주관리, 품목라우팅 등
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┬──────────────────────────────┐
|
||||||
|
│ v2-table-list │ v2-table-list 또는 폼 │
|
||||||
|
│ (마스터) │ (디테일) │
|
||||||
|
│ ─────────────── │ │
|
||||||
|
│ □ A001 항목1 │ [상세 정보] │
|
||||||
|
│ □ A002 항목2 ← │ │
|
||||||
|
│ □ A003 항목3 │ │
|
||||||
|
└──────────────────┴──────────────────────────────┘
|
||||||
|
v2-split-panel-layout
|
||||||
|
```
|
||||||
|
|
||||||
|
**필수 컴포넌트**:
|
||||||
|
- `v2-split-panel-layout` (1개)
|
||||||
|
- `v2-table-list` (2개: 마스터, 디테일)
|
||||||
|
|
||||||
|
**설정 포인트**:
|
||||||
|
- `splitRatio`: 좌우 비율 (기본 30:70)
|
||||||
|
- `relation.type`: join / detail / custom
|
||||||
|
- `relation.foreignKey`: 연결 키 컬럼
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 패턴 C: 마스터-디테일 + 탭
|
||||||
|
|
||||||
|
**적용 화면**: 거래처관리, 품목정보, 설비정보 등
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┬──────────────────────────────┐
|
||||||
|
│ v2-table-list │ v2-tabs-widget │
|
||||||
|
│ (마스터) │ ┌────┬────┬────┐ │
|
||||||
|
│ │ │기본│이력│첨부│ │
|
||||||
|
│ □ A001 거래처1 │ └────┴────┴────┘ │
|
||||||
|
│ □ A002 거래처2 ← │ [탭별 컨텐츠] │
|
||||||
|
└──────────────────┴──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**필수 컴포넌트**:
|
||||||
|
- `v2-split-panel-layout` (1개)
|
||||||
|
- `v2-table-list` (1개: 마스터)
|
||||||
|
- `v2-tabs-widget` (1개)
|
||||||
|
|
||||||
|
**설정 포인트**:
|
||||||
|
- 탭별 표시할 테이블/폼 설정
|
||||||
|
- 마스터 선택 시 탭 컨텐츠 연동
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 패턴 D: 카드 뷰
|
||||||
|
|
||||||
|
**적용 화면**: 설비정보, 대시보드, 상품 카탈로그 등
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ v2-table-search-widget │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ v2-card-display │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||||
|
│ │ [이미지]│ │ [이미지]│ │ [이미지]│ │
|
||||||
|
│ │ 제목 │ │ 제목 │ │ 제목 │ │
|
||||||
|
│ │ 설명 │ │ 설명 │ │ 설명 │ │
|
||||||
|
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**필수 컴포넌트**:
|
||||||
|
- `v2-table-search-widget` (1개)
|
||||||
|
- `v2-card-display` (1개)
|
||||||
|
|
||||||
|
**설정 포인트**:
|
||||||
|
- `cardsPerRow`: 한 행당 카드 수
|
||||||
|
- `columnMapping`: 제목, 부제목, 이미지, 상태 필드 매핑
|
||||||
|
- `cardStyle`: 이미지 위치, 크기
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 패턴 E: 피벗 분석
|
||||||
|
|
||||||
|
**적용 화면**: 매출분석, 재고현황, 생산실적 분석 등
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ v2-pivot-grid │
|
||||||
|
│ │ 2024년 │ 2025년 │ 2026년 │ 합계 │
|
||||||
|
│ ─────────────────────────────────────────────── │
|
||||||
|
│ 지역A │ 1,000 │ 1,200 │ 1,500 │ 3,700 │
|
||||||
|
│ 지역B │ 2,000 │ 2,500 │ 3,000 │ 7,500 │
|
||||||
|
│ 합계 │ 3,000 │ 3,700 │ 4,500 │ 11,200 │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**필수 컴포넌트**:
|
||||||
|
- `v2-pivot-grid` (1개)
|
||||||
|
|
||||||
|
**설정 포인트**:
|
||||||
|
- `fields[].area`: row / column / data / filter
|
||||||
|
- `fields[].summaryType`: sum / avg / count / min / max
|
||||||
|
- `fields[].groupInterval`: 날짜 그룹화 (year/quarter/month)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 회사별 개발 시 핵심 체크포인트
|
||||||
|
|
||||||
|
### 4.1 테이블 설계 확인
|
||||||
|
|
||||||
|
**가장 먼저 확인**:
|
||||||
|
1. 회사에서 사용할 테이블 목록
|
||||||
|
2. 테이블 간 관계 (FK)
|
||||||
|
3. 조회 조건으로 쓸 컬럼
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ 체크리스트:
|
||||||
|
□ 테이블명이 DB에 존재하는가?
|
||||||
|
□ company_code 컬럼이 있는가? (멀티테넌시)
|
||||||
|
□ 마스터-디테일 관계의 FK가 정의되어 있는가?
|
||||||
|
□ 검색 대상 컬럼에 인덱스가 있는가?
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 화면 패턴 판단
|
||||||
|
|
||||||
|
**질문을 통한 판단**:
|
||||||
|
|
||||||
|
| 질문 | 예 → 패턴 |
|
||||||
|
|------|----------|
|
||||||
|
| 단일 테이블만 조회/편집? | 패턴 A (기본 마스터) |
|
||||||
|
| 마스터 선택 시 디테일 표시? | 패턴 B (마스터-디테일) |
|
||||||
|
| 상세에 탭이 필요? | 패턴 C (마스터-디테일+탭) |
|
||||||
|
| 이미지+정보 카드 형태? | 패턴 D (카드 뷰) |
|
||||||
|
| 다차원 집계/분석? | 패턴 E (피벗) |
|
||||||
|
|
||||||
|
### 4.3 컴포넌트 설정 필수 항목
|
||||||
|
|
||||||
|
#### v2-table-list 필수 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
selectedTable: "테이블명", // 필수
|
||||||
|
columns: [ // 표시할 컬럼
|
||||||
|
{ columnName: "id", displayName: "ID", visible: true, sortable: true },
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
enabled: true,
|
||||||
|
pageSize: 20,
|
||||||
|
showSizeSelector: true,
|
||||||
|
showPageInfo: true
|
||||||
|
},
|
||||||
|
displayMode: "table", // "table" | "card"
|
||||||
|
checkbox: {
|
||||||
|
enabled: true,
|
||||||
|
multiple: true,
|
||||||
|
position: "left",
|
||||||
|
selectAll: true
|
||||||
|
},
|
||||||
|
horizontalScroll: { // 가로 스크롤 설정
|
||||||
|
enabled: true,
|
||||||
|
maxVisibleColumns: 8
|
||||||
|
},
|
||||||
|
linkedFilters: [], // 연결 필터 (다른 컴포넌트와 연동)
|
||||||
|
excludeFilter: {}, // 제외 필터
|
||||||
|
autoLoad: true, // 자동 데이터 로드
|
||||||
|
stickyHeader: false, // 헤더 고정
|
||||||
|
autoWidth: true // 자동 너비 조정
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### v2-split-panel-layout 필수 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
leftPanel: {
|
||||||
|
displayMode: "table", // "list" | "table" | "custom"
|
||||||
|
tableName: "마스터_테이블명",
|
||||||
|
columns: [], // 컬럼 설정
|
||||||
|
editButton: { // 수정 버튼 설정
|
||||||
|
enabled: true,
|
||||||
|
mode: "auto", // "auto" | "modal"
|
||||||
|
modalScreenId: "" // 모달 모드 시 화면 ID
|
||||||
|
},
|
||||||
|
addButton: { // 추가 버튼 설정
|
||||||
|
enabled: true,
|
||||||
|
mode: "auto",
|
||||||
|
modalScreenId: ""
|
||||||
|
},
|
||||||
|
deleteButton: { // 삭제 버튼 설정
|
||||||
|
enabled: true,
|
||||||
|
buttonLabel: "삭제",
|
||||||
|
confirmMessage: "삭제하시겠습니까?"
|
||||||
|
},
|
||||||
|
addModalColumns: [], // 추가 모달 전용 컬럼
|
||||||
|
additionalTabs: [] // 추가 탭 설정
|
||||||
|
},
|
||||||
|
rightPanel: {
|
||||||
|
displayMode: "table",
|
||||||
|
tableName: "디테일_테이블명",
|
||||||
|
relation: {
|
||||||
|
type: "detail", // "join" | "detail" | "custom"
|
||||||
|
foreignKey: "master_id", // 연결 키
|
||||||
|
leftColumn: "", // 좌측 연결 컬럼
|
||||||
|
rightColumn: "", // 우측 연결 컬럼
|
||||||
|
keys: [] // 복합 키
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitRatio: 30, // 좌측 비율 (0-100)
|
||||||
|
resizable: true, // 리사이즈 가능
|
||||||
|
minLeftWidth: 200, // 좌측 최소 너비
|
||||||
|
minRightWidth: 300, // 우측 최소 너비
|
||||||
|
syncSelection: true, // 선택 동기화
|
||||||
|
autoLoad: true // 자동 로드
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### v2-split-panel-layout 커스텀 모드 (NEW)
|
||||||
|
|
||||||
|
패널 내부에 자유롭게 컴포넌트를 배치할 수 있습니다. (v2-tabs-widget과 동일 구조)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
leftPanel: {
|
||||||
|
displayMode: "custom", // 커스텀 모드 활성화
|
||||||
|
components: [ // 내부 컴포넌트 배열
|
||||||
|
{
|
||||||
|
id: "btn-save",
|
||||||
|
componentType: "v2-button-primary",
|
||||||
|
label: "저장",
|
||||||
|
position: { x: 10, y: 10 },
|
||||||
|
size: { width: 100, height: 40 },
|
||||||
|
componentConfig: { buttonAction: "save" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "tbl-list",
|
||||||
|
componentType: "v2-table-list",
|
||||||
|
label: "목록",
|
||||||
|
position: { x: 10, y: 60 },
|
||||||
|
size: { width: 400, height: 300 },
|
||||||
|
componentConfig: { selectedTable: "테이블명" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
rightPanel: {
|
||||||
|
displayMode: "table" // 기존 모드 유지
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**디자인 모드 기능**:
|
||||||
|
- 컴포넌트 클릭 → 좌측 설정 패널에서 속성 편집
|
||||||
|
- 드래그 핸들(상단)로 이동
|
||||||
|
- 리사이즈 핸들(모서리)로 크기 조절
|
||||||
|
- 실제 컴포넌트 미리보기 렌더링
|
||||||
|
|
||||||
|
#### v2-card-display 필수 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
dataSource: "table",
|
||||||
|
columnMapping: {
|
||||||
|
title: "name", // 제목 필드
|
||||||
|
subtitle: "code", // 부제목 필드
|
||||||
|
image: "image_url", // 이미지 필드 (선택)
|
||||||
|
status: "status" // 상태 필드 (선택)
|
||||||
|
},
|
||||||
|
cardsPerRow: 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 공통 컴포넌트 한계점
|
||||||
|
|
||||||
|
### 5.1 현재 불가능한 기능
|
||||||
|
|
||||||
|
| 기능 | 상태 | 대안 |
|
||||||
|
|------|------|------|
|
||||||
|
| 트리 뷰 (BOM, 조직도) | ❌ 미지원 | 테이블로 대체 or 별도 개발 |
|
||||||
|
| 드래그앤드롭 정렬 | ❌ 미지원 | 순서 컬럼으로 대체 |
|
||||||
|
| 인라인 편집 | ⚠️ 제한적 | 모달 편집으로 대체 |
|
||||||
|
| 복잡한 차트 | ❌ 미지원 | 외부 라이브러리 연동 |
|
||||||
|
|
||||||
|
> **v1.1 업데이트**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)가 추가되어 해당 기능은 이제 지원됩니다.
|
||||||
|
|
||||||
|
### 5.2 권장하지 않는 조합
|
||||||
|
|
||||||
|
| 조합 | 이유 |
|
||||||
|
|------|------|
|
||||||
|
| 3단계 이상 중첩 분할 | 화면 복잡도 증가, 성능 저하 |
|
||||||
|
| 탭 안에 탭 | 사용성 저하 |
|
||||||
|
| 한 화면에 3개 이상 테이블 | 데이터 로딩 성능 |
|
||||||
|
| 피벗 + 상세 테이블 동시 | 데이터 과부하 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 제어관리 (비즈니스 로직) - 별도 설정 필수
|
||||||
|
|
||||||
|
> **핵심**: V2 컴포넌트는 **UI만 담당**합니다. 비즈니스 로직은 **제어관리**에서 별도 설정해야 합니다.
|
||||||
|
|
||||||
|
### 6.1 UI vs 제어 분리 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 화면 구성 │
|
||||||
|
├─────────────────────────────┬───────────────────────────────────┤
|
||||||
|
│ UI 레이아웃 │ 제어관리 │
|
||||||
|
│ (screen_layouts_v2) │ (dataflow_diagrams) │
|
||||||
|
├─────────────────────────────┼───────────────────────────────────┤
|
||||||
|
│ • 컴포넌트 배치 │ • 버튼 클릭 시 액션 │
|
||||||
|
│ • 검색 필드 구성 │ • INSERT/UPDATE/DELETE 설정 │
|
||||||
|
│ • 테이블 컬럼 표시 │ • 조건부 실행 │
|
||||||
|
│ • 카드/탭 레이아웃 │ • 다중 행 처리 │
|
||||||
|
│ │ • 테이블 간 데이터 이동 │
|
||||||
|
└─────────────────────────────┴───────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 HTML에서 파악 가능/불가능
|
||||||
|
|
||||||
|
| 구분 | HTML에서 파악 | 이유 |
|
||||||
|
|------|--------------|------|
|
||||||
|
| 컴포넌트 배치 | ✅ 가능 | HTML 구조에서 보임 |
|
||||||
|
| 검색 필드 | ✅ 가능 | input 태그로 확인 |
|
||||||
|
| 테이블 컬럼 | ✅ 가능 | thead에서 확인 |
|
||||||
|
| **저장 테이블** | ❌ 불가능 | JS/백엔드에서 처리 |
|
||||||
|
| **버튼 액션** | ❌ 불가능 | 제어관리에서 설정 |
|
||||||
|
| **전/후 처리** | ❌ 불가능 | 제어관리에서 설정 |
|
||||||
|
| **다중 행 처리** | ❌ 불가능 | 제어관리에서 설정 |
|
||||||
|
| **테이블 간 관계** | ❌ 불가능 | DB/제어관리에서 설정 |
|
||||||
|
|
||||||
|
### 6.3 제어관리 설정 항목
|
||||||
|
|
||||||
|
#### 트리거 타입
|
||||||
|
- **버튼 클릭 전 (before)**: 클릭 직전 실행
|
||||||
|
- **버튼 클릭 후 (after)**: 클릭 완료 후 실행
|
||||||
|
|
||||||
|
#### 액션 타입
|
||||||
|
- **INSERT**: 새로운 데이터 삽입
|
||||||
|
- **UPDATE**: 기존 데이터 수정
|
||||||
|
- **DELETE**: 데이터 삭제
|
||||||
|
|
||||||
|
#### 조건 설정
|
||||||
|
```typescript
|
||||||
|
// 예: 선택된 행의 상태가 '대기'인 경우에만 실행
|
||||||
|
{
|
||||||
|
field: "status",
|
||||||
|
operator: "=",
|
||||||
|
value: "대기",
|
||||||
|
dataType: "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 필드 매핑
|
||||||
|
```typescript
|
||||||
|
// 예: 소스 테이블의 값을 타겟 테이블로 이동
|
||||||
|
{
|
||||||
|
sourceTable: "order_master",
|
||||||
|
sourceField: "order_no",
|
||||||
|
targetTable: "order_history",
|
||||||
|
targetField: "order_no"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 제어관리 예시: 수주 확정 버튼
|
||||||
|
|
||||||
|
**시나리오**: 수주 목록에서 3건 선택 후 [확정] 버튼 클릭
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [확정] 버튼 클릭 │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 1. 조건 체크: status = '대기' 인 행만 │
|
||||||
|
│ 2. UPDATE order_master SET status = '확정' WHERE id IN (선택) │
|
||||||
|
│ 3. INSERT order_history (수주이력 테이블에 기록) │
|
||||||
|
│ 4. 외부 시스템 호출 (ERP 연동) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**제어관리 설정**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"triggerType": "after",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"actionType": "update",
|
||||||
|
"targetTable": "order_master",
|
||||||
|
"conditions": [{ "field": "status", "operator": "=", "value": "대기" }],
|
||||||
|
"fieldMappings": [{ "targetField": "status", "defaultValue": "확정" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"actionType": "insert",
|
||||||
|
"targetTable": "order_history",
|
||||||
|
"fieldMappings": [
|
||||||
|
{ "sourceField": "order_no", "targetField": "order_no" },
|
||||||
|
{ "sourceField": "customer_name", "targetField": "customer_name" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 회사별 개발 시 제어관리 체크리스트
|
||||||
|
|
||||||
|
```
|
||||||
|
□ 버튼별 액션 정의
|
||||||
|
- 어떤 버튼이 있는가?
|
||||||
|
- 각 버튼 클릭 시 무슨 동작?
|
||||||
|
|
||||||
|
□ 저장/수정/삭제 대상 테이블
|
||||||
|
- 메인 테이블은?
|
||||||
|
- 이력 테이블은?
|
||||||
|
- 연관 테이블은?
|
||||||
|
|
||||||
|
□ 조건부 실행
|
||||||
|
- 특정 상태일 때만 실행?
|
||||||
|
- 특정 값 체크 필요?
|
||||||
|
|
||||||
|
□ 다중 행 처리
|
||||||
|
- 여러 행 선택 후 일괄 처리?
|
||||||
|
- 각 행별 개별 처리?
|
||||||
|
|
||||||
|
□ 외부 연동
|
||||||
|
- ERP/MES 등 외부 시스템 호출?
|
||||||
|
- API 연동 필요?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 회사별 커스터마이징 영역
|
||||||
|
|
||||||
|
### 7.1 컴포넌트로 처리되는 영역 (표준화)
|
||||||
|
|
||||||
|
| 영역 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| UI 레이아웃 | 컴포넌트 배치, 크기, 위치 |
|
||||||
|
| 검색 조건 | 화면 디자이너에서 설정 |
|
||||||
|
| 테이블 컬럼 | 표시/숨김, 순서, 너비 |
|
||||||
|
| 기본 CRUD | 조회, 저장, 삭제 자동 처리 |
|
||||||
|
| 페이지네이션 | 자동 처리 |
|
||||||
|
| 정렬/필터 | 자동 처리 |
|
||||||
|
|
||||||
|
### 7.2 회사별 개발 필요 영역
|
||||||
|
|
||||||
|
| 영역 | 설명 | 개발 방법 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 비즈니스 로직 | 저장 전/후 검증, 계산 | 데이터플로우 또는 백엔드 API |
|
||||||
|
| 특수 UI | 간트, 트리, 차트 등 | 별도 컴포넌트 개발 |
|
||||||
|
| 외부 연동 | ERP, MES 등 연계 | 외부 호출 설정 |
|
||||||
|
| 리포트/인쇄 | 전표, 라벨 출력 | 리포트 컴포넌트 |
|
||||||
|
| 결재 프로세스 | 승인/반려 흐름 | 워크플로우 설정 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 빠른 개발 가이드
|
||||||
|
|
||||||
|
### Step 1: 화면 분석
|
||||||
|
1. 어떤 테이블을 사용하는가?
|
||||||
|
2. 테이블 간 관계는?
|
||||||
|
3. 어떤 패턴인가? (A/B/C/D/E)
|
||||||
|
|
||||||
|
### Step 2: 컴포넌트 배치
|
||||||
|
1. 화면 디자이너에서 패턴에 맞는 컴포넌트 배치
|
||||||
|
2. 각 컴포넌트에 테이블/컬럼 설정
|
||||||
|
|
||||||
|
### Step 3: 연동 설정
|
||||||
|
1. 마스터-디테일 관계 설정 (FK)
|
||||||
|
2. 검색 조건 설정
|
||||||
|
3. 버튼 액션 설정
|
||||||
|
|
||||||
|
### Step 4: 테스트
|
||||||
|
1. 데이터 조회 확인
|
||||||
|
2. 마스터 선택 시 디테일 연동 확인
|
||||||
|
3. 저장/삭제 동작 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 요약
|
||||||
|
|
||||||
|
### V2 컴포넌트 커버리지
|
||||||
|
|
||||||
|
| 화면 유형 | 지원 여부 | 주요 컴포넌트 |
|
||||||
|
|-----------|----------|--------------|
|
||||||
|
| 기본 CRUD | ✅ 완전 | v2-table-list, v2-table-search-widget |
|
||||||
|
| 마스터-디테일 | ✅ 완전 | v2-split-panel-layout |
|
||||||
|
| 탭 화면 | ✅ 완전 | v2-tabs-widget |
|
||||||
|
| 카드 뷰 | ✅ 완전 | v2-card-display |
|
||||||
|
| 피벗 분석 | ✅ 완전 | v2-pivot-grid |
|
||||||
|
| 그룹화 테이블 | ✅ 지원 | v2-table-grouped |
|
||||||
|
| 타임라인/스케줄 | ✅ 지원 | v2-timeline-scheduler |
|
||||||
|
| 파일 업로드 | ✅ 지원 | v2-file-upload |
|
||||||
|
| 트리 뷰 | ❌ 미지원 | 개발 필요 |
|
||||||
|
|
||||||
|
### 개발 시 핵심 원칙
|
||||||
|
|
||||||
|
1. **테이블 먼저**: DB 테이블 구조 확인이 최우선
|
||||||
|
2. **패턴 판단**: 5가지 패턴 중 어디에 해당하는지 판단
|
||||||
|
3. **표준 조합**: 검증된 컴포넌트 조합 사용
|
||||||
|
4. **한계 인식**: 불가능한 UI는 조기에 식별하여 별도 개발 계획
|
||||||
|
5. **멀티테넌시**: 모든 테이블에 company_code 필터링 필수
|
||||||
|
6. **제어관리 필수**: UI 완성 후 버튼별 비즈니스 로직 설정 필수
|
||||||
|
|
||||||
|
### UI vs 제어 구분
|
||||||
|
|
||||||
|
| 영역 | 담당 | 설정 위치 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 화면 레이아웃 | V2 컴포넌트 | 화면 디자이너 |
|
||||||
|
| 비즈니스 로직 | 제어관리 | dataflow_diagrams |
|
||||||
|
| 외부 연동 | 외부호출 설정 | external_call_configs |
|
||||||
|
|
||||||
|
**HTML에서 배낄 수 있는 것**: UI 구조만
|
||||||
|
**별도 설정 필요한 것**: 저장 테이블, 버튼 액션, 조건 처리, 다중 행 처리
|
||||||
|
|
@ -1,952 +0,0 @@
|
||||||
# WACE 화면 시스템 - DB 스키마 & 컴포넌트 설정 전체 레퍼런스
|
|
||||||
|
|
||||||
> **최종 업데이트**: 2026-03-13
|
|
||||||
> **용도**: AI 챗봇이 화면 생성 시 참조하는 DB 스키마, 컴포넌트 전체 설정 사전
|
|
||||||
> **관련 문서**: `v2-component-usage-guide.md` (SQL 템플릿, 실행 예시)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 시스템 아키텍처
|
|
||||||
|
|
||||||
### 렌더링 파이프라인
|
|
||||||
|
|
||||||
```
|
|
||||||
[DB] screen_definitions + screen_layouts_v2
|
|
||||||
→ [Backend API] GET /api/screens/:screenId
|
|
||||||
→ [layoutV2Converter] V2 JSON → Legacy 변환 (기본값 + overrides 병합)
|
|
||||||
→ [ResponsiveGridRenderer] → DynamicComponentRenderer
|
|
||||||
→ [ComponentRegistry] → 실제 React 컴포넌트
|
|
||||||
```
|
|
||||||
|
|
||||||
### 테이블 관계도
|
|
||||||
|
|
||||||
```
|
|
||||||
비즈니스 테이블 ←── table_labels (라벨)
|
|
||||||
←── table_type_columns (컬럼 타입, company_code='*')
|
|
||||||
←── column_labels (한글 라벨)
|
|
||||||
|
|
||||||
screen_definitions ←── screen_layouts_v2 (layout_data JSON)
|
|
||||||
menu_info (메뉴 트리, menu_url → /screen/{screen_code})
|
|
||||||
|
|
||||||
[선택] dataflow_diagrams (비즈니스 로직)
|
|
||||||
[선택] numbering_rules + numbering_rule_parts (채번)
|
|
||||||
[선택] table_column_category_values (카테고리)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. DB 테이블 스키마
|
|
||||||
|
|
||||||
### 2.1 비즈니스 테이블 필수 구조
|
|
||||||
|
|
||||||
> **[최우선 규칙] 비즈니스 테이블에 NOT NULL / UNIQUE 제약조건 절대 금지!**
|
|
||||||
>
|
|
||||||
> 멀티테넌시 환경에서 회사별로 필수값/유니크 규칙이 다를 수 있으므로,
|
|
||||||
> 제약조건은 DB 레벨이 아닌 **`table_type_columns`의 메타데이터(`is_nullable`, `is_unique`)로 논리적 제어**한다.
|
|
||||||
> DB에 직접 NOT NULL/UNIQUE/CHECK/FOREIGN KEY를 걸면 멀티테넌시가 깨진다.
|
|
||||||
>
|
|
||||||
> **허용**: `id` PRIMARY KEY, `DEFAULT` 값만 DB 레벨 설정
|
|
||||||
> **금지**: 비즈니스 컬럼에 `NOT NULL`, `UNIQUE`, `CHECK`, `FOREIGN KEY`
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE "{테이블명}" (
|
|
||||||
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
||||||
"created_date" timestamp DEFAULT now(),
|
|
||||||
"updated_date" timestamp DEFAULT now(),
|
|
||||||
"writer" varchar(500) DEFAULT NULL,
|
|
||||||
"company_code" varchar(500),
|
|
||||||
-- 모든 비즈니스 컬럼은 varchar(500), NOT NULL/UNIQUE 제약조건 금지
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 table_labels
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| table_name | varchar PK | 테이블명 |
|
|
||||||
| table_label | varchar | 한글 라벨 |
|
|
||||||
| description | text | 설명 |
|
|
||||||
| use_log_table | varchar(1) | 'Y'/'N' |
|
|
||||||
|
|
||||||
### 2.3 table_type_columns
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| id | serial PK | 자동 증가 |
|
|
||||||
| table_name | varchar | UNIQUE(+column_name+company_code) |
|
|
||||||
| column_name | varchar | 컬럼명 |
|
|
||||||
| company_code | varchar | `'*'` = 전체 공통 |
|
|
||||||
| input_type | varchar | text/number/date/code/entity/select/checkbox/radio/textarea/category/numbering |
|
|
||||||
| detail_settings | text | JSON (code/entity/select 상세) |
|
|
||||||
| is_nullable | varchar | `'Y'`/`'N'` (논리적 필수값 제어) |
|
|
||||||
| display_order | integer | -5~-1: 기본, 0~: 비즈니스 |
|
|
||||||
| column_label | varchar | 컬럼 한글 라벨 |
|
|
||||||
| description | text | 컬럼 설명 |
|
|
||||||
| is_visible | boolean | 화면 표시 여부 (기본 true) |
|
|
||||||
| code_category | varchar | input_type=code일 때 코드 카테고리 |
|
|
||||||
| code_value | varchar | 코드 값 |
|
|
||||||
| reference_table | varchar | input_type=entity일 때 참조 테이블 |
|
|
||||||
| reference_column | varchar | 참조 컬럼 |
|
|
||||||
| display_column | varchar | 참조 표시 컬럼 |
|
|
||||||
| is_unique | varchar | `'Y'`/`'N'` (논리적 유니크 제어) |
|
|
||||||
| category_ref | varchar | 카테고리 참조 |
|
|
||||||
|
|
||||||
### 2.4 screen_definitions
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| screen_id | serial PK | 자동 증가 |
|
|
||||||
| screen_name | varchar NOT NULL | 화면명 |
|
|
||||||
| screen_code | varchar | **조건부 UNIQUE** (`WHERE is_active <> 'D'`) |
|
|
||||||
| table_name | varchar | 메인 테이블명 |
|
|
||||||
| company_code | varchar NOT NULL | 회사 코드 |
|
|
||||||
| description | text | 화면 설명 |
|
|
||||||
| is_active | char(1) | `'Y'`/`'N'`/`'D'` (D=삭제) |
|
|
||||||
| layout_metadata | jsonb | 레이아웃 메타데이터 |
|
|
||||||
| created_date | timestamp | 생성일시 |
|
|
||||||
| created_by | varchar | 생성자 |
|
|
||||||
| updated_date | timestamp | 수정일시 |
|
|
||||||
| updated_by | varchar | 수정자 |
|
|
||||||
| deleted_date | timestamp | 삭제일시 |
|
|
||||||
| deleted_by | varchar | 삭제자 |
|
|
||||||
| delete_reason | text | 삭제 사유 |
|
|
||||||
| db_source_type | varchar | `'internal'` (기본) / `'external'` |
|
|
||||||
| db_connection_id | integer | 외부 DB 연결 ID |
|
|
||||||
| data_source_type | varchar | `'database'` (기본) / `'rest_api'` |
|
|
||||||
| rest_api_connection_id | integer | REST API 연결 ID |
|
|
||||||
| rest_api_endpoint | varchar | REST API 엔드포인트 |
|
|
||||||
| rest_api_json_path | varchar | JSON 응답 경로 (기본 `'data'`) |
|
|
||||||
| source_screen_id | integer | 원본 화면 ID (복사본일 때) |
|
|
||||||
|
|
||||||
> **screen_code UNIQUE 주의**: `is_active = 'D'`(삭제)인 화면은 UNIQUE 대상에서 제외된다. 삭제된 화면과 같은 코드로 새 화면을 만들 수 있지만, 활성 상태(`'Y'`/`'N'`)에서는 중복 불가.
|
|
||||||
|
|
||||||
### 2.5 screen_layouts_v2
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| layout_id | serial PK | 자동 증가 |
|
|
||||||
| screen_id | integer FK | UNIQUE(+company_code+layer_id) |
|
|
||||||
| company_code | varchar NOT NULL | 회사 코드 |
|
|
||||||
| layout_data | jsonb NOT NULL | 전체 레이아웃 JSON (기본 `'{}'`) |
|
|
||||||
| created_at | timestamptz | 생성일시 |
|
|
||||||
| updated_at | timestamptz | 수정일시 |
|
|
||||||
| layer_id | integer | 1=기본 레이어 (기본값 1) |
|
|
||||||
| layer_name | varchar | 레이어명 (기본 `'기본 레이어'`) |
|
|
||||||
| condition_config | jsonb | 레이어 조건부 표시 설정 |
|
|
||||||
|
|
||||||
### 2.6 menu_info
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| objid | numeric PK | BIGINT 고유값 |
|
|
||||||
| menu_type | numeric | 0=화면, 1=폴더 |
|
|
||||||
| parent_obj_id | numeric | 부모 메뉴 objid |
|
|
||||||
| menu_name_kor | varchar | 메뉴명 (한글) |
|
|
||||||
| menu_name_eng | varchar | 메뉴명 (영문) |
|
|
||||||
| seq | numeric | 정렬 순서 |
|
|
||||||
| menu_url | varchar | `/screen/{screen_code}` |
|
|
||||||
| menu_desc | varchar | 메뉴 설명 |
|
|
||||||
| writer | varchar | 작성자 |
|
|
||||||
| regdate | timestamp | 등록일시 |
|
|
||||||
| status | varchar | 상태 (`'active'` 등) |
|
|
||||||
| company_code | varchar | 회사 코드 (기본 `'*'`) |
|
|
||||||
| screen_code | varchar | 연결 화면 코드 |
|
|
||||||
| system_name | varchar | 시스템명 |
|
|
||||||
| lang_key | varchar | 다국어 키 |
|
|
||||||
| lang_key_desc | varchar | 다국어 설명 키 |
|
|
||||||
| menu_code | varchar | 메뉴 코드 |
|
|
||||||
| source_menu_objid | bigint | 원본 메뉴 objid (복사본일 때) |
|
|
||||||
| screen_group_id | integer | 화면 그룹 ID |
|
|
||||||
| menu_icon | varchar | 메뉴 아이콘 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 컴포넌트 전체 설정 레퍼런스 (32개)
|
|
||||||
|
|
||||||
> 아래 설정은 layout_data JSON의 각 컴포넌트 `overrides` 안에 들어가는 값이다.
|
|
||||||
> 기본값과 다른 부분만 overrides에 지정하면 된다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.1 v2-table-list (데이터 테이블)
|
|
||||||
|
|
||||||
**용도**: DB 테이블 데이터를 테이블/카드 형태로 조회/편집. 가장 핵심적인 컴포넌트.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| tableName | string | - | 조회할 DB 테이블명 |
|
|
||||||
| selectedTable | string | - | tableName 별칭 |
|
|
||||||
| displayMode | `"table"\|"card"` | `"table"` | 테이블 모드 또는 카드 모드 |
|
|
||||||
| autoLoad | boolean | `true` | 화면 로드 시 자동으로 데이터 조회 |
|
|
||||||
| isReadOnly | boolean | false | 읽기 전용 (편집 불가) |
|
|
||||||
| columns | ColumnConfig[] | `[]` | 표시할 컬럼 설정 배열 |
|
|
||||||
| title | string | - | 테이블 상단 제목 |
|
|
||||||
| showHeader | boolean | `true` | 테이블 헤더 행 표시 |
|
|
||||||
| showFooter | boolean | `true` | 테이블 푸터 표시 |
|
|
||||||
| height | string | `"auto"` | 높이 모드 (`"auto"`, `"fixed"`, `"viewport"`) |
|
|
||||||
| fixedHeight | number | - | height="fixed"일 때 고정 높이(px) |
|
|
||||||
| autoWidth | boolean | `true` | 컬럼 너비 자동 계산 |
|
|
||||||
| stickyHeader | boolean | `false` | 스크롤 시 헤더 고정 |
|
|
||||||
|
|
||||||
**checkbox (체크박스 설정)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| enabled | boolean | `true` | 체크박스 사용 여부 |
|
|
||||||
| multiple | boolean | `true` | 다중 선택 허용 |
|
|
||||||
| position | `"left"\|"right"` | `"left"` | 체크박스 위치 |
|
|
||||||
| selectAll | boolean | `true` | 전체 선택 버튼 표시 |
|
|
||||||
|
|
||||||
**pagination (페이지네이션)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| enabled | boolean | `true` | 페이지네이션 사용 |
|
|
||||||
| pageSize | number | `20` | 한 페이지당 행 수 |
|
|
||||||
| showSizeSelector | boolean | `true` | 페이지 크기 변경 드롭다운 |
|
|
||||||
| showPageInfo | boolean | `true` | "1-20 / 100건" 같은 정보 표시 |
|
|
||||||
| pageSizeOptions | number[] | `[10,20,50,100]` | 선택 가능한 페이지 크기 |
|
|
||||||
|
|
||||||
**horizontalScroll (가로 스크롤)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| enabled | boolean | `true` | 가로 스크롤 사용 |
|
|
||||||
| maxVisibleColumns | number | `8` | 스크롤 없이 보이는 최대 컬럼 수 |
|
|
||||||
| minColumnWidth | number | `100` | 컬럼 최소 너비(px) |
|
|
||||||
| maxColumnWidth | number | `300` | 컬럼 최대 너비(px) |
|
|
||||||
|
|
||||||
**tableStyle (스타일)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| theme | string | `"default"` | 테마 (`default`/`striped`/`bordered`/`minimal`) |
|
|
||||||
| headerStyle | string | `"default"` | 헤더 스타일 (`default`/`dark`/`light`) |
|
|
||||||
| rowHeight | string | `"normal"` | 행 높이 (`compact`/`normal`/`comfortable`) |
|
|
||||||
| alternateRows | boolean | `true` | 짝수/홀수 행 색상 교차 |
|
|
||||||
| hoverEffect | boolean | `true` | 마우스 호버 시 행 강조 |
|
|
||||||
| borderStyle | string | `"light"` | 테두리 (`none`/`light`/`heavy`) |
|
|
||||||
|
|
||||||
**toolbar (툴바 버튼)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| showEditMode | boolean | `false` | 즉시저장/배치저장 모드 전환 버튼 |
|
|
||||||
| showExcel | boolean | `false` | Excel 내보내기 버튼 |
|
|
||||||
| showPdf | boolean | `false` | PDF 내보내기 버튼 |
|
|
||||||
| showSearch | boolean | `false` | 테이블 내 검색 |
|
|
||||||
| showRefresh | boolean | `false` | 상단 새로고침 버튼 |
|
|
||||||
| showPaginationRefresh | boolean | `true` | 하단 새로고침 버튼 |
|
|
||||||
|
|
||||||
**filter (필터)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| enabled | boolean | `true` | 필터 기능 사용 |
|
|
||||||
| filters | array | `[]` | 사전 정의 필터 목록 |
|
|
||||||
|
|
||||||
**ColumnConfig (columns 배열 요소)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| columnName | string | DB 컬럼명 |
|
|
||||||
| displayName | string | 화면 표시명 |
|
|
||||||
| visible | boolean | 표시 여부 |
|
|
||||||
| sortable | boolean | 정렬 가능 여부 |
|
|
||||||
| searchable | boolean | 검색 가능 여부 |
|
|
||||||
| editable | boolean | 인라인 편집 가능 여부 |
|
|
||||||
| width | number | 컬럼 너비(px) |
|
|
||||||
| align | `"left"\|"center"\|"right"` | 텍스트 정렬 |
|
|
||||||
| format | string | 포맷 (`text`/`number`/`date`/`currency`/`boolean`) |
|
|
||||||
| hidden | boolean | 숨김 (데이터는 로드하되 표시 안 함) |
|
|
||||||
| fixed | `"left"\|"right"\|false` | 컬럼 고정 위치 |
|
|
||||||
| thousandSeparator | boolean | 숫자 천 단위 콤마 |
|
|
||||||
| isEntityJoin | boolean | 엔티티 조인 사용 여부 |
|
|
||||||
| entityJoinInfo | object | 조인 정보 (`sourceTable`, `sourceColumn`, `referenceTable`, `joinAlias`) |
|
|
||||||
|
|
||||||
**cardConfig (displayMode="card"일 때)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| idColumn | string | `"id"` | ID 컬럼 |
|
|
||||||
| titleColumn | string | `"name"` | 카드 제목 컬럼 |
|
|
||||||
| subtitleColumn | string | - | 부제목 컬럼 |
|
|
||||||
| descriptionColumn | string | - | 설명 컬럼 |
|
|
||||||
| imageColumn | string | - | 이미지 URL 컬럼 |
|
|
||||||
| cardsPerRow | number | `3` | 행당 카드 수 |
|
|
||||||
| cardSpacing | number | `16` | 카드 간격(px) |
|
|
||||||
| showActions | boolean | `true` | 카드 액션 버튼 표시 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.2 v2-split-panel-layout (마스터-디테일 분할)
|
|
||||||
|
|
||||||
**용도**: 좌측 마스터 테이블 선택 → 우측 디테일 테이블 연동. 가장 복잡한 컴포넌트.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| splitRatio | number | `30` | 좌측 패널 비율(0~100) |
|
|
||||||
| resizable | boolean | `true` | 사용자가 분할선 드래그로 비율 변경 가능 |
|
|
||||||
| minLeftWidth | number | `200` | 좌측 최소 너비(px) |
|
|
||||||
| minRightWidth | number | `300` | 우측 최소 너비(px) |
|
|
||||||
| autoLoad | boolean | `true` | 화면 로드 시 자동 데이터 조회 |
|
|
||||||
| syncSelection | boolean | `true` | 좌측 선택 시 우측 자동 갱신 |
|
|
||||||
|
|
||||||
**leftPanel / rightPanel 공통 설정**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| title | string | 패널 제목 |
|
|
||||||
| tableName | string | DB 테이블명 |
|
|
||||||
| displayMode | `"list"\|"table"\|"custom"` | `list`: 리스트, `table`: 테이블, `custom`: 자유 배치 |
|
|
||||||
| columns | array | 컬럼 설정 (`name`, `label`, `width`, `sortable`, `align`, `isEntityJoin`, `joinInfo`) |
|
|
||||||
| showSearch | boolean | 패널 내 검색 바 표시 |
|
|
||||||
| showAdd | boolean | 추가 버튼 표시 |
|
|
||||||
| showEdit | boolean | 수정 버튼 표시 |
|
|
||||||
| showDelete | boolean | 삭제 버튼 표시 |
|
|
||||||
| addButton | object | `{ enabled, mode("auto"/"modal"), modalScreenId }` |
|
|
||||||
| editButton | object | `{ enabled, mode("auto"/"modal"), modalScreenId, buttonLabel }` |
|
|
||||||
| deleteButton | object | `{ enabled, buttonLabel, confirmMessage }` |
|
|
||||||
| addModalColumns | array | 추가 모달 전용 컬럼 (`name`, `label`, `required`) |
|
|
||||||
| dataFilter | object | `{ enabled, filters, matchType("all"/"any") }` |
|
|
||||||
| tableConfig | object | `{ showCheckbox, showRowNumber, rowHeight, headerHeight, striped, bordered, hoverable, stickyHeader }` |
|
|
||||||
| components | array | displayMode="custom"일 때 내부 컴포넌트 배열 |
|
|
||||||
|
|
||||||
**rightPanel 전용 설정**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| relation | object | 마스터-디테일 연결 관계 |
|
|
||||||
| relation.type | `"detail"\|"join"` | detail: FK 관계, join: 테이블 JOIN |
|
|
||||||
| relation.leftColumn | string | 좌측(마스터) 연결 컬럼 (보통 `"id"`) |
|
|
||||||
| relation.rightColumn | string | 우측(디테일) 연결 컬럼 (FK) |
|
|
||||||
| relation.foreignKey | string | FK 컬럼명 (rightColumn과 동일) |
|
|
||||||
| relation.keys | array | 복합키 `[{ leftColumn, rightColumn }]` |
|
|
||||||
| additionalTabs | array | 우측 패널에 탭 추가 (각 탭은 rightPanel과 동일 구조 + `tabId`, `label`) |
|
|
||||||
| addConfig | object | `{ targetTable, autoFillColumns, leftPanelColumn, targetColumn }` |
|
|
||||||
| deduplication | object | `{ enabled, groupByColumn, keepStrategy, sortColumn }` |
|
|
||||||
| summaryColumnCount | number | 요약 표시 컬럼 수 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.3 v2-table-search-widget (검색 바)
|
|
||||||
|
|
||||||
**용도**: 테이블 상단에 배치하여 검색/필터 기능 제공. 대상 테이블 컬럼을 자동 감지.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| autoSelectFirstTable | boolean | `true` | 화면 내 첫 번째 테이블 자동 연결 |
|
|
||||||
| showTableSelector | boolean | `true` | 테이블 선택 드롭다운 표시 |
|
|
||||||
| title | string | `"테이블 검색"` | 검색 바 제목 |
|
|
||||||
| filterMode | `"dynamic"\|"preset"` | `"dynamic"` | dynamic: 자동 필터, preset: 고정 필터 |
|
|
||||||
| presetFilters | array | `[]` | 고정 필터 목록 (`{ columnName, columnLabel, filterType, width }`) |
|
|
||||||
| targetPanelPosition | `"left"\|"right"\|"auto"` | `"left"` | split-panel에서 대상 패널 위치 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.4 v2-input (텍스트/숫자 입력)
|
|
||||||
|
|
||||||
**용도**: 텍스트, 숫자, 비밀번호, textarea, 슬라이더, 컬러, 버튼 등 단일 값 입력.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| inputType | string | `"text"` | 입력 유형: `text`/`number`/`password`/`slider`/`color`/`button`/`textarea` |
|
|
||||||
| format | string | `"none"` | 포맷 검증: `none`/`email`/`tel`/`url`/`currency`/`biz_no` |
|
|
||||||
| placeholder | string | `""` | 입력 힌트 텍스트 |
|
|
||||||
| required | boolean | `false` | 필수 입력 표시 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| disabled | boolean | `false` | 비활성화 |
|
|
||||||
| maxLength | number | - | 최대 입력 글자 수 |
|
|
||||||
| minLength | number | - | 최소 입력 글자 수 |
|
|
||||||
| pattern | string | - | 정규식 패턴 검증 |
|
|
||||||
| showCounter | boolean | `false` | 글자 수 카운터 표시 |
|
|
||||||
| min | number | - | 최소값 (number/slider) |
|
|
||||||
| max | number | - | 최대값 (number/slider) |
|
|
||||||
| step | number | - | 증감 단위 (number/slider) |
|
|
||||||
| buttonText | string | - | 버튼 텍스트 (inputType=button) |
|
|
||||||
| tableName | string | - | 바인딩 테이블명 |
|
|
||||||
| columnName | string | - | 바인딩 컬럼명 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.5 v2-select (선택)
|
|
||||||
|
|
||||||
**용도**: 드롭다운, 콤보박스, 라디오, 체크박스, 태그, 토글 등 선택형 입력.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| mode | string | `"dropdown"` | 선택 모드: `dropdown`/`combobox`/`radio`/`check`/`tag`/`tagbox`/`toggle`/`swap` |
|
|
||||||
| source | string | `"distinct"` | 데이터 소스: `static`/`code`/`db`/`api`/`entity`/`category`/`distinct`/`select` |
|
|
||||||
| options | array | `[]` | source=static일 때 옵션 목록 `[{ label, value }]` |
|
|
||||||
| codeGroup | string | - | source=code일 때 코드 그룹 |
|
|
||||||
| codeCategory | string | - | source=code일 때 코드 카테고리 |
|
|
||||||
| table | string | - | source=db일 때 테이블명 |
|
|
||||||
| valueColumn | string | - | source=db일 때 값 컬럼 |
|
|
||||||
| labelColumn | string | - | source=db일 때 표시 컬럼 |
|
|
||||||
| entityTable | string | - | source=entity일 때 엔티티 테이블 |
|
|
||||||
| entityValueField | string | - | source=entity일 때 값 필드 |
|
|
||||||
| entityLabelField | string | - | source=entity일 때 표시 필드 |
|
|
||||||
| searchable | boolean | `true` | 검색 가능 (combobox에서 기본 활성) |
|
|
||||||
| multiple | boolean | `false` | 다중 선택 허용 |
|
|
||||||
| maxSelect | number | - | 최대 선택 수 |
|
|
||||||
| allowClear | boolean | - | 선택 해제 허용 |
|
|
||||||
| placeholder | string | `"선택하세요"` | 힌트 텍스트 |
|
|
||||||
| required | boolean | `false` | 필수 선택 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| disabled | boolean | `false` | 비활성화 |
|
|
||||||
| cascading | object | - | 연쇄 선택 (상위 select 값에 따라 하위 옵션 변경) |
|
|
||||||
| hierarchical | boolean | - | 계층 구조 (부모-자식 관계) |
|
|
||||||
| parentField | string | - | 부모 필드명 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.6 v2-date (날짜)
|
|
||||||
|
|
||||||
**용도**: 날짜, 시간, 날짜시간, 날짜범위, 월, 연도 입력.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dateType | string | `"date"` | 날짜 유형: `date`/`datetime`/`time`/`daterange`/`month`/`year` |
|
|
||||||
| format | string | `"YYYY-MM-DD"` | 표시/저장 형식 |
|
|
||||||
| placeholder | string | `"날짜 선택"` | 힌트 텍스트 |
|
|
||||||
| required | boolean | `false` | 필수 입력 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| disabled | boolean | `false` | 비활성화 |
|
|
||||||
| showTime | boolean | `false` | 시간 선택 표시 (datetime) |
|
|
||||||
| use24Hours | boolean | `true` | 24시간 형식 |
|
|
||||||
| range | boolean | - | 범위 선택 (시작~종료) |
|
|
||||||
| minDate | string | - | 선택 가능 최소 날짜 (ISO 8601) |
|
|
||||||
| maxDate | string | - | 선택 가능 최대 날짜 |
|
|
||||||
| showToday | boolean | - | 오늘 버튼 표시 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.7 v2-button-primary (액션 버튼)
|
|
||||||
|
|
||||||
**용도**: 저장, 삭제, 조회, 커스텀 등 액션 버튼. 제어관리(dataflow)와 연결 가능.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| text | string | `"저장"` | 버튼 텍스트 |
|
|
||||||
| actionType | string | `"button"` | 버튼 타입: `button`/`submit`/`reset` |
|
|
||||||
| variant | string | `"primary"` | 스타일: `primary`/`secondary`/`danger` |
|
|
||||||
| size | string | `"md"` | 크기: `sm`/`md`/`lg` |
|
|
||||||
| disabled | boolean | `false` | 비활성화 |
|
|
||||||
| action | object | - | 액션 설정 |
|
|
||||||
| action.type | string | `"save"` | 액션 유형: `save`/`delete`/`edit`/`copy`/`navigate`/`modal`/`control`/`custom` |
|
|
||||||
| action.successMessage | string | `"저장되었습니다."` | 성공 시 토스트 메시지 |
|
|
||||||
| action.errorMessage | string | `"오류가 발생했습니다."` | 실패 시 토스트 메시지 |
|
|
||||||
| webTypeConfig | object | - | 제어관리 연결 설정 |
|
|
||||||
| webTypeConfig.enableDataflowControl | boolean | - | 제어관리 활성화 |
|
|
||||||
| webTypeConfig.dataflowConfig | object | - | 제어관리 설정 |
|
|
||||||
| webTypeConfig.dataflowConfig.controlMode | string | - | `"relationship"`/`"flow"`/`"none"` |
|
|
||||||
| webTypeConfig.dataflowConfig.relationshipConfig | object | - | `{ relationshipId, executionTiming("before"/"after"/"replace") }` |
|
|
||||||
| webTypeConfig.dataflowConfig.flowConfig | object | - | `{ flowId, executionTiming }` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.8 v2-table-grouped (그룹화 테이블)
|
|
||||||
|
|
||||||
**용도**: 특정 컬럼 기준으로 데이터를 그룹화. 그룹별 접기/펼치기, 집계 표시.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| selectedTable | string | `""` | DB 테이블명 |
|
|
||||||
| columns | array | `[]` | 컬럼 설정 (v2-table-list와 동일) |
|
|
||||||
| showCheckbox | boolean | `false` | 체크박스 표시 |
|
|
||||||
| checkboxMode | `"single"\|"multi"` | `"multi"` | 체크박스 모드 |
|
|
||||||
| isReadOnly | boolean | `false` | 읽기 전용 |
|
|
||||||
| rowClickable | boolean | `true` | 행 클릭 가능 |
|
|
||||||
| showExpandAllButton | boolean | `true` | 전체 펼치기/접기 버튼 |
|
|
||||||
| groupHeaderStyle | string | `"default"` | 그룹 헤더 스타일 (`default`/`compact`/`card`) |
|
|
||||||
| emptyMessage | string | `"데이터가 없습니다."` | 빈 데이터 메시지 |
|
|
||||||
| height | string\|number | `"auto"` | 높이 |
|
|
||||||
| maxHeight | number | `600` | 최대 높이(px) |
|
|
||||||
| pagination.enabled | boolean | `false` | 페이지네이션 사용 |
|
|
||||||
| pagination.pageSize | number | `10` | 페이지 크기 |
|
|
||||||
|
|
||||||
**groupConfig (그룹화 설정)**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| groupByColumn | string | `""` | **필수**. 그룹화 기준 컬럼 |
|
|
||||||
| groupLabelFormat | string | `"{value}"` | 그룹 라벨 포맷 |
|
|
||||||
| defaultExpanded | boolean | `true` | 초기 펼침 여부 |
|
|
||||||
| sortDirection | `"asc"\|"desc"` | `"asc"` | 그룹 정렬 방향 |
|
|
||||||
| summary.showCount | boolean | `true` | 그룹별 건수 표시 |
|
|
||||||
| summary.sumColumns | string[] | `[]` | 합계 표시할 컬럼 목록 |
|
|
||||||
| summary.avgColumns | string[] | - | 평균 표시 컬럼 |
|
|
||||||
| summary.maxColumns | string[] | - | 최대값 표시 컬럼 |
|
|
||||||
| summary.minColumns | string[] | - | 최소값 표시 컬럼 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.9 v2-pivot-grid (피벗 분석)
|
|
||||||
|
|
||||||
**용도**: 다차원 데이터 분석. 행/열/데이터/필터 영역에 필드를 배치하여 집계.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| fields | array | `[]` | **필수**. 피벗 필드 배열 |
|
|
||||||
| dataSource | object | - | 데이터 소스 (`type`, `tableName`, `joinConfigs`, `filterConditions`) |
|
|
||||||
| allowSortingBySummary | boolean | - | 집계값 기준 정렬 허용 |
|
|
||||||
| allowFiltering | boolean | - | 필터링 허용 |
|
|
||||||
| allowExpandAll | boolean | - | 전체 확장/축소 허용 |
|
|
||||||
| wordWrapEnabled | boolean | - | 텍스트 줄바꿈 |
|
|
||||||
| height | string\|number | - | 높이 |
|
|
||||||
| totals.showRowGrandTotals | boolean | - | 행 총합계 표시 |
|
|
||||||
| totals.showColumnGrandTotals | boolean | - | 열 총합계 표시 |
|
|
||||||
| chart.enabled | boolean | - | 차트 연동 표시 |
|
|
||||||
| chart.type | string | - | 차트 타입 (`bar`/`line`/`area`/`pie`/`stackedBar`) |
|
|
||||||
|
|
||||||
**fields 배열 요소**:
|
|
||||||
|
|
||||||
| 설정 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| field | string | DB 컬럼명 |
|
|
||||||
| caption | string | 표시 라벨 |
|
|
||||||
| area | `"row"\|"column"\|"data"\|"filter"` | **필수**. 배치 영역 |
|
|
||||||
| summaryType | string | area=data일 때: `sum`/`count`/`avg`/`min`/`max`/`countDistinct` |
|
|
||||||
| groupInterval | string | 날짜 그룹화: `year`/`quarter`/`month`/`week`/`day` |
|
|
||||||
| sortBy | string | 정렬 기준: `value`/`caption` |
|
|
||||||
| sortOrder | string | 정렬 방향: `asc`/`desc`/`none` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.10 v2-card-display (카드 뷰)
|
|
||||||
|
|
||||||
**용도**: 테이블 데이터를 카드 형태로 표시. 이미지+제목+설명 구조.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSource | string | `"table"` | 데이터 소스: `table`/`static` |
|
|
||||||
| tableName | string | - | DB 테이블명 |
|
|
||||||
| cardsPerRow | number | `3` | 행당 카드 수 (1~6) |
|
|
||||||
| cardSpacing | number | `16` | 카드 간격(px) |
|
|
||||||
| columnMapping | object | `{}` | 필드 매핑 (`title`, `subtitle`, `description`, `image`, `status`) |
|
|
||||||
| cardStyle.showTitle | boolean | `true` | 제목 표시 |
|
|
||||||
| cardStyle.showSubtitle | boolean | `true` | 부제목 표시 |
|
|
||||||
| cardStyle.showDescription | boolean | `true` | 설명 표시 |
|
|
||||||
| cardStyle.showImage | boolean | `false` | 이미지 표시 |
|
|
||||||
| cardStyle.showActions | boolean | `true` | 액션 버튼 표시 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.11 v2-timeline-scheduler (간트차트)
|
|
||||||
|
|
||||||
**용도**: 시간축 기반 일정/계획 시각화. 드래그/리사이즈로 일정 편집.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| selectedTable | string | - | 스케줄 데이터 테이블 |
|
|
||||||
| resourceTable | string | `"equipment_mng"` | 리소스(설비/작업자) 테이블 |
|
|
||||||
| scheduleType | string | `"PRODUCTION"` | 스케줄 유형: `PRODUCTION`/`MAINTENANCE`/`SHIPPING`/`WORK_ASSIGN` |
|
|
||||||
| defaultZoomLevel | string | `"day"` | 초기 줌: `day`/`week`/`month` |
|
|
||||||
| editable | boolean | `true` | 편집 가능 |
|
|
||||||
| draggable | boolean | `true` | 드래그 이동 허용 |
|
|
||||||
| resizable | boolean | `true` | 기간 리사이즈 허용 |
|
|
||||||
| rowHeight | number | `50` | 행 높이(px) |
|
|
||||||
| headerHeight | number | `60` | 헤더 높이(px) |
|
|
||||||
| resourceColumnWidth | number | `150` | 리소스 컬럼 너비(px) |
|
|
||||||
| cellWidth.day | number | `60` | 일 단위 셀 너비 |
|
|
||||||
| cellWidth.week | number | `120` | 주 단위 셀 너비 |
|
|
||||||
| cellWidth.month | number | `40` | 월 단위 셀 너비 |
|
|
||||||
| showConflicts | boolean | `true` | 시간 겹침 충돌 표시 |
|
|
||||||
| showProgress | boolean | `true` | 진행률 바 표시 |
|
|
||||||
| showTodayLine | boolean | `true` | 오늘 날짜 표시선 |
|
|
||||||
| showToolbar | boolean | `true` | 상단 툴바 표시 |
|
|
||||||
| showAddButton | boolean | `true` | 추가 버튼 |
|
|
||||||
| height | number | `500` | 높이(px) |
|
|
||||||
|
|
||||||
**fieldMapping (필수)**:
|
|
||||||
|
|
||||||
| 설정 | 기본값 | 설명 |
|
|
||||||
|------|--------|------|
|
|
||||||
| id | `"schedule_id"` | 스케줄 PK 필드 |
|
|
||||||
| resourceId | `"resource_id"` | 리소스 FK 필드 |
|
|
||||||
| title | `"schedule_name"` | 제목 필드 |
|
|
||||||
| startDate | `"start_date"` | 시작일 필드 |
|
|
||||||
| endDate | `"end_date"` | 종료일 필드 |
|
|
||||||
| status | - | 상태 필드 |
|
|
||||||
| progress | - | 진행률 필드 (0~100) |
|
|
||||||
|
|
||||||
**resourceFieldMapping**:
|
|
||||||
|
|
||||||
| 설정 | 기본값 | 설명 |
|
|
||||||
|------|--------|------|
|
|
||||||
| id | `"equipment_code"` | 리소스 PK |
|
|
||||||
| name | `"equipment_name"` | 리소스 표시명 |
|
|
||||||
| group | - | 리소스 그룹 |
|
|
||||||
|
|
||||||
**statusColors (상태별 색상)**:
|
|
||||||
|
|
||||||
| 상태 | 기본 색상 |
|
|
||||||
|------|----------|
|
|
||||||
| planned | `"#3b82f6"` (파랑) |
|
|
||||||
| in_progress | `"#f59e0b"` (주황) |
|
|
||||||
| completed | `"#10b981"` (초록) |
|
|
||||||
| delayed | `"#ef4444"` (빨강) |
|
|
||||||
| cancelled | `"#6b7280"` (회색) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.12 v2-tabs-widget (탭)
|
|
||||||
|
|
||||||
**용도**: 탭 전환. 각 탭 내부에 컴포넌트 배치 가능.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| tabs | array | `[{id:"tab-1",label:"탭1",...}]` | 탭 배열 |
|
|
||||||
| defaultTab | string | `"tab-1"` | 기본 활성 탭 ID |
|
|
||||||
| orientation | string | `"horizontal"` | 탭 방향: `horizontal`/`vertical` |
|
|
||||||
| variant | string | `"default"` | 스타일: `default`/`pills`/`underline` |
|
|
||||||
| allowCloseable | boolean | `false` | 탭 닫기 버튼 표시 |
|
|
||||||
| persistSelection | boolean | `false` | 탭 선택 상태 localStorage 저장 |
|
|
||||||
|
|
||||||
**tabs 배열 요소**: `{ id, label, order, disabled, icon, components[] }`
|
|
||||||
**components 요소**: `{ id, componentType, label, position, size, componentConfig }`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.13 v2-aggregation-widget (집계 카드)
|
|
||||||
|
|
||||||
**용도**: 합계, 평균, 개수 등 집계값을 카드 형태로 표시.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSourceType | string | `"table"` | 데이터 소스: `table`/`component`/`selection` |
|
|
||||||
| tableName | string | - | 테이블명 |
|
|
||||||
| items | array | `[]` | 집계 항목 배열 |
|
|
||||||
| layout | string | `"horizontal"` | 배치: `horizontal`/`vertical` |
|
|
||||||
| showLabels | boolean | `true` | 라벨 표시 |
|
|
||||||
| showIcons | boolean | `true` | 아이콘 표시 |
|
|
||||||
| gap | string | `"16px"` | 항목 간격 |
|
|
||||||
| autoRefresh | boolean | `false` | 자동 새로고침 |
|
|
||||||
| refreshOnFormChange | boolean | `true` | 폼 변경 시 새로고침 |
|
|
||||||
|
|
||||||
**items 요소**: `{ id, columnName, columnLabel, type("sum"/"avg"/"count"/"max"/"min"), format, decimalPlaces, prefix, suffix }`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.14 v2-status-count (상태별 건수)
|
|
||||||
|
|
||||||
**용도**: 상태별 건수를 카드 형태로 표시. 대시보드/현황 화면용.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| title | string | `"상태 현황"` | 제목 |
|
|
||||||
| tableName | string | `""` | 대상 테이블 |
|
|
||||||
| statusColumn | string | `"status"` | 상태 컬럼명 |
|
|
||||||
| relationColumn | string | `""` | 관계 컬럼 (필터용) |
|
|
||||||
| items | array | - | 상태 항목 `[{ value, label, color }]` |
|
|
||||||
| showTotal | boolean | - | 합계 표시 |
|
|
||||||
| cardSize | string | `"md"` | 카드 크기: `sm`/`md`/`lg` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.15 v2-text-display (텍스트 표시)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| text | string | `"텍스트를 입력하세요"` | 표시 텍스트 |
|
|
||||||
| fontSize | string | `"14px"` | 폰트 크기 |
|
|
||||||
| fontWeight | string | `"normal"` | 폰트 굵기 |
|
|
||||||
| color | string | `"#212121"` | 텍스트 색상 |
|
|
||||||
| textAlign | string | `"left"` | 정렬: `left`/`center`/`right` |
|
|
||||||
| backgroundColor | string | - | 배경색 |
|
|
||||||
| padding | string | - | 패딩 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.16 v2-numbering-rule (자동 채번)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| ruleConfig | object | - | 채번 규칙 설정 |
|
|
||||||
| maxRules | number | `6` | 최대 파트 수 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| showPreview | boolean | `true` | 미리보기 표시 |
|
|
||||||
| showRuleList | boolean | `true` | 규칙 목록 표시 |
|
|
||||||
| cardLayout | string | `"vertical"` | 레이아웃: `vertical`/`horizontal` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.17 v2-file-upload (파일 업로드)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| placeholder | string | `"파일을 선택하세요"` | 힌트 텍스트 |
|
|
||||||
| multiple | boolean | `true` | 다중 업로드 |
|
|
||||||
| accept | string | `"*/*"` | 허용 파일 형식 (예: `"image/*"`, `".pdf,.xlsx"`) |
|
|
||||||
| maxSize | number | `10485760` | 최대 파일 크기(bytes, 기본 10MB) |
|
|
||||||
| maxFiles | number | - | 최대 파일 수 |
|
|
||||||
| showPreview | boolean | - | 미리보기 표시 |
|
|
||||||
| showFileList | boolean | - | 파일 목록 표시 |
|
|
||||||
| allowDelete | boolean | - | 삭제 허용 |
|
|
||||||
| allowDownload | boolean | - | 다운로드 허용 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.18 v2-section-card (그룹 컨테이너 - 테두리)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| title | string | `"섹션 제목"` | 제목 |
|
|
||||||
| description | string | `""` | 설명 |
|
|
||||||
| showHeader | boolean | `true` | 헤더 표시 |
|
|
||||||
| padding | string | `"md"` | 패딩: `none`/`sm`/`md`/`lg` |
|
|
||||||
| backgroundColor | string | `"default"` | 배경: `default`/`muted`/`transparent` |
|
|
||||||
| borderStyle | string | `"solid"` | 테두리: `solid`/`dashed`/`none` |
|
|
||||||
| collapsible | boolean | `false` | 접기/펼치기 가능 |
|
|
||||||
| defaultOpen | boolean | `true` | 기본 펼침 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.19 v2-section-paper (그룹 컨테이너 - 배경색)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| backgroundColor | string | `"default"` | 배경: `default`/`muted`/`accent`/`primary`/`custom` |
|
|
||||||
| customColor | string | - | custom일 때 색상 |
|
|
||||||
| showBorder | boolean | `false` | 테두리 표시 |
|
|
||||||
| padding | string | `"md"` | 패딩: `none`/`sm`/`md`/`lg` |
|
|
||||||
| roundedCorners | string | `"md"` | 모서리: `none`/`sm`/`md`/`lg` |
|
|
||||||
| shadow | string | `"none"` | 그림자: `none`/`sm`/`md` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.20 v2-divider-line (구분선)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| orientation | string | - | 방향 (가로/세로) |
|
|
||||||
| thickness | number | - | 두께 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.21 v2-split-line (캔버스 분할선)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| resizable | boolean | `true` | 드래그 리사이즈 허용 |
|
|
||||||
| lineColor | string | `"#e2e8f0"` | 분할선 색상 |
|
|
||||||
| lineWidth | number | `4` | 분할선 두께(px) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.22 v2-repeat-container (반복 렌더링)
|
|
||||||
|
|
||||||
**용도**: 데이터 수만큼 내부 컴포넌트를 반복 렌더링. 카드 리스트 등에 사용.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSourceType | string | `"manual"` | 소스: `table-list`/`v2-repeater`/`externalData`/`manual` |
|
|
||||||
| dataSourceComponentId | string | - | 연결할 컴포넌트 ID |
|
|
||||||
| tableName | string | - | 테이블명 |
|
|
||||||
| layout | string | `"vertical"` | 배치: `vertical`/`horizontal`/`grid` |
|
|
||||||
| gridColumns | number | `2` | grid일 때 컬럼 수 |
|
|
||||||
| gap | string | `"16px"` | 아이템 간격 |
|
|
||||||
| showBorder | boolean | `true` | 카드 테두리 |
|
|
||||||
| showShadow | boolean | `false` | 카드 그림자 |
|
|
||||||
| borderRadius | string | `"8px"` | 모서리 둥글기 |
|
|
||||||
| backgroundColor | string | `"#ffffff"` | 배경색 |
|
|
||||||
| padding | string | `"16px"` | 패딩 |
|
|
||||||
| showItemTitle | boolean | `false` | 아이템 제목 표시 |
|
|
||||||
| itemTitleTemplate | string | `""` | 제목 템플릿 (예: `"{order_no} - {item}"`) |
|
|
||||||
| emptyMessage | string | `"데이터가 없습니다"` | 빈 상태 메시지 |
|
|
||||||
| clickable | boolean | `false` | 클릭 가능 |
|
|
||||||
| selectionMode | string | `"single"` | 선택 모드: `single`/`multiple` |
|
|
||||||
| usePaging | boolean | `false` | 페이징 사용 |
|
|
||||||
| pageSize | number | `10` | 페이지 크기 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.23 v2-repeater (반복 데이터 관리)
|
|
||||||
|
|
||||||
**용도**: 인라인/모달 모드로 반복 데이터(주문 상세 등) 관리. 행 추가/삭제/편집.
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| renderMode | string | `"inline"` | 모드: `inline` (인라인 편집) / `modal` (모달로 선택 추가) |
|
|
||||||
| mainTableName | string | - | 저장 대상 테이블 |
|
|
||||||
| foreignKeyColumn | string | - | 마스터 연결 FK 컬럼 |
|
|
||||||
| foreignKeySourceColumn | string | - | 마스터 PK 컬럼 |
|
|
||||||
| columns | array | `[]` | 컬럼 설정 |
|
|
||||||
| dataSource.tableName | string | - | 데이터 테이블 |
|
|
||||||
| dataSource.foreignKey | string | - | FK 컬럼 |
|
|
||||||
| dataSource.sourceTable | string | - | 모달용 소스 테이블 |
|
|
||||||
| modal.size | string | `"md"` | 모달 크기: `sm`/`md`/`lg`/`xl`/`full` |
|
|
||||||
| modal.title | string | - | 모달 제목 |
|
|
||||||
| modal.searchFields | string[] | - | 검색 필드 |
|
|
||||||
| features.showAddButton | boolean | `true` | 추가 버튼 |
|
|
||||||
| features.showDeleteButton | boolean | `true` | 삭제 버튼 |
|
|
||||||
| features.inlineEdit | boolean | `false` | 인라인 편집 |
|
|
||||||
| features.showRowNumber | boolean | `false` | 행 번호 표시 |
|
|
||||||
| calculationRules | array | - | 자동 계산 규칙 (예: 수량*단가=금액) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.24 v2-approval-step (결재 스테퍼)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| targetTable | string | `""` | 결재 대상 테이블 |
|
|
||||||
| targetRecordIdField | string | `""` | 레코드 ID 필드 |
|
|
||||||
| displayMode | string | `"horizontal"` | 표시 방향: `horizontal`/`vertical` |
|
|
||||||
| showComment | boolean | `true` | 결재 코멘트 표시 |
|
|
||||||
| showTimestamp | boolean | `true` | 결재 시간 표시 |
|
|
||||||
| showDept | boolean | `true` | 부서 표시 |
|
|
||||||
| compact | boolean | `false` | 컴팩트 모드 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.25 v2-bom-tree (BOM 트리)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| detailTable | string | `"bom_detail"` | BOM 디테일 테이블 |
|
|
||||||
| foreignKey | string | `"bom_id"` | BOM 마스터 FK |
|
|
||||||
| parentKey | string | `"parent_detail_id"` | 트리 부모 키 (자기참조) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.26 v2-bom-item-editor (BOM 편집)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| detailTable | string | `"bom_detail"` | BOM 디테일 테이블 |
|
|
||||||
| sourceTable | string | `"item_info"` | 품목 소스 테이블 |
|
|
||||||
| foreignKey | string | `"bom_id"` | BOM 마스터 FK |
|
|
||||||
| parentKey | string | `"parent_detail_id"` | 트리 부모 키 |
|
|
||||||
| itemCodeField | string | `"item_number"` | 품목 코드 필드 |
|
|
||||||
| itemNameField | string | `"item_name"` | 품목명 필드 |
|
|
||||||
| itemTypeField | string | `"type"` | 품목 유형 필드 |
|
|
||||||
| itemUnitField | string | `"unit"` | 품목 단위 필드 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.27 v2-category-manager (카테고리 관리)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| tableName | string | - | 대상 테이블 |
|
|
||||||
| columnName | string | - | 카테고리 컬럼 |
|
|
||||||
| menuObjid | number | - | 연결 메뉴 OBJID |
|
|
||||||
| viewMode | string | `"tree"` | 뷰 모드: `tree`/`list` |
|
|
||||||
| showViewModeToggle | boolean | `true` | 뷰 모드 토글 표시 |
|
|
||||||
| defaultExpandLevel | number | `1` | 기본 트리 펼침 레벨 |
|
|
||||||
| showInactiveItems | boolean | `false` | 비활성 항목 표시 |
|
|
||||||
| leftPanelWidth | number | `15` | 좌측 패널 너비 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.28 v2-media (미디어)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| mediaType | string | `"file"` | 미디어 타입: `file`/`image`/`video`/`audio` |
|
|
||||||
| multiple | boolean | `false` | 다중 업로드 |
|
|
||||||
| preview | boolean | `true` | 미리보기 |
|
|
||||||
| maxSize | number | `10` | 최대 크기(MB) |
|
|
||||||
| accept | string | `"*/*"` | 허용 형식 |
|
|
||||||
| showFileList | boolean | `true` | 파일 목록 |
|
|
||||||
| dragDrop | boolean | `true` | 드래그앤드롭 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.29 v2-location-swap-selector (위치 교환)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSource.type | string | `"static"` | 소스: `static`/`table`/`code` |
|
|
||||||
| dataSource.tableName | string | - | 장소 테이블 |
|
|
||||||
| dataSource.valueField | string | `"location_code"` | 값 필드 |
|
|
||||||
| dataSource.labelField | string | `"location_name"` | 표시 필드 |
|
|
||||||
| dataSource.staticOptions | array | - | 정적 옵션 `[{value, label}]` |
|
|
||||||
| departureField | string | `"departure"` | 출발지 저장 필드 |
|
|
||||||
| destinationField | string | `"destination"` | 도착지 저장 필드 |
|
|
||||||
| departureLabel | string | `"출발지"` | 출발지 라벨 |
|
|
||||||
| destinationLabel | string | `"도착지"` | 도착지 라벨 |
|
|
||||||
| showSwapButton | boolean | `true` | 교환 버튼 표시 |
|
|
||||||
| variant | string | `"card"` | UI: `card`/`inline`/`minimal` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.30 v2-rack-structure (창고 랙)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| maxConditions | number | `10` | 최대 조건 수 |
|
|
||||||
| maxRows | number | `99` | 최대 열 수 |
|
|
||||||
| maxLevels | number | `20` | 최대 단 수 |
|
|
||||||
| codePattern | string | `"{warehouseCode}-{floor}{zone}-{row:02d}-{level}"` | 위치 코드 패턴 |
|
|
||||||
| namePattern | string | `"{zone}구역-{row:02d}열-{level}단"` | 위치 이름 패턴 |
|
|
||||||
| showTemplates | boolean | `true` | 템플릿 표시 |
|
|
||||||
| showPreview | boolean | `true` | 미리보기 |
|
|
||||||
| showStatistics | boolean | `true` | 통계 카드 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.31 v2-process-work-standard (공정 작업기준)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSource.itemTable | string | `"item_info"` | 품목 테이블 |
|
|
||||||
| dataSource.routingVersionTable | string | `"item_routing_version"` | 라우팅 버전 테이블 |
|
|
||||||
| dataSource.routingDetailTable | string | `"item_routing_detail"` | 라우팅 디테일 테이블 |
|
|
||||||
| dataSource.processTable | string | `"process_mng"` | 공정 테이블 |
|
|
||||||
| splitRatio | number | `30` | 좌우 분할 비율 |
|
|
||||||
| leftPanelTitle | string | `"품목 및 공정 선택"` | 좌측 패널 제목 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| itemListMode | string | `"all"` | 품목 모드: `all`/`registered` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3.32 v2-item-routing (품목 라우팅)
|
|
||||||
|
|
||||||
| 설정 | 타입 | 기본값 | 설명 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| dataSource.itemTable | string | `"item_info"` | 품목 테이블 |
|
|
||||||
| dataSource.routingVersionTable | string | `"item_routing_version"` | 라우팅 버전 테이블 |
|
|
||||||
| dataSource.routingDetailTable | string | `"item_routing_detail"` | 라우팅 디테일 테이블 |
|
|
||||||
| dataSource.processTable | string | `"process_mng"` | 공정 테이블 |
|
|
||||||
| splitRatio | number | `40` | 좌우 분할 비율 |
|
|
||||||
| leftPanelTitle | string | `"품목 목록"` | 좌측 제목 |
|
|
||||||
| rightPanelTitle | string | `"공정 순서"` | 우측 제목 |
|
|
||||||
| readonly | boolean | `false` | 읽기 전용 |
|
|
||||||
| autoSelectFirstVersion | boolean | `true` | 첫 버전 자동 선택 |
|
|
||||||
| itemListMode | string | `"all"` | 품목 모드: `all`/`registered` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 패턴 의사결정 트리
|
|
||||||
|
|
||||||
```
|
|
||||||
Q1. 시간축 기반 일정/간트차트? → v2-timeline-scheduler
|
|
||||||
Q2. 다차원 피벗 분석? → v2-pivot-grid
|
|
||||||
Q3. 그룹별 접기/펼치기? → v2-table-grouped
|
|
||||||
Q4. 카드 형태 표시? → v2-card-display
|
|
||||||
Q5. 마스터-디테일?
|
|
||||||
├ 우측 멀티 탭? → v2-split-panel-layout + additionalTabs
|
|
||||||
└ 단일 디테일? → v2-split-panel-layout
|
|
||||||
Q6. 단일 테이블? → v2-table-search-widget + v2-table-list
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 관계(relation) 레퍼런스
|
|
||||||
|
|
||||||
| 관계 유형 | 설정 |
|
|
||||||
|----------|------|
|
|
||||||
| 단순 FK | `{ type:"detail", leftColumn:"id", rightColumn:"{FK}", foreignKey:"{FK}" }` |
|
|
||||||
| 복합 키 | `{ type:"detail", keys:[{ leftColumn:"a", rightColumn:"b" }] }` |
|
|
||||||
| JOIN | `{ type:"join", leftColumn:"{col}", rightColumn:"{col}" }` |
|
|
||||||
|
|
||||||
## 6. 엔티티 조인
|
|
||||||
|
|
||||||
FK 컬럼에 참조 테이블의 이름을 표시:
|
|
||||||
|
|
||||||
**table_type_columns**: `input_type='entity'`, `detail_settings='{"referenceTable":"X","referenceColumn":"id","displayColumn":"name"}'`
|
|
||||||
|
|
||||||
**layout_data columns**: `{ name:"fk_col", isEntityJoin:true, joinInfo:{ sourceTable:"A", sourceColumn:"fk_col", referenceTable:"X", joinAlias:"name" } }`
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -583,7 +583,7 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
const needsStripBorder = isV2HorizLabel || isButtonComponent;
|
const needsStripBorder = isV2HorizLabel || isButtonComponent;
|
||||||
const safeComponentStyle = needsStripBorder
|
const safeComponentStyle = needsStripBorder
|
||||||
? (() => {
|
? (() => {
|
||||||
const { borderWidth, borderColor, borderStyle, border, borderRadius, ...rest } = componentStyle as any;
|
const { borderWidth, borderColor, borderStyle, border, ...rest } = componentStyle as any;
|
||||||
return rest;
|
return rest;
|
||||||
})()
|
})()
|
||||||
: componentStyle;
|
: componentStyle;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Slider } from "@/components/ui/slider";
|
import { Slider } from "@/components/ui/slider";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { V2InputProps, V2InputConfig, V2InputFormat } from "@/types/v2-components";
|
import { V2InputProps, V2InputConfig, V2InputFormat } from "@/types/v2-components";
|
||||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||||
|
|
@ -62,11 +61,11 @@ export function validateInputFormat(value: string, format: V2InputFormat): { isV
|
||||||
return { isValid, errorMessage: isValid ? "" : formatConfig.errorMessage };
|
return { isValid, errorMessage: isValid ? "" : formatConfig.errorMessage };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 통화 형식 변환 (공통 formatNumber 사용)
|
// 통화 형식 변환
|
||||||
function formatCurrency(value: string | number): string {
|
function formatCurrency(value: string | number): string {
|
||||||
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
|
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
|
||||||
if (isNaN(num)) return "";
|
if (isNaN(num)) return "";
|
||||||
return centralFormatNumber(num);
|
return num.toLocaleString("ko-KR");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사업자번호 형식 변환
|
// 사업자번호 형식 변환
|
||||||
|
|
@ -235,22 +234,7 @@ const TextInput = forwardRef<
|
||||||
TextInput.displayName = "TextInput";
|
TextInput.displayName = "TextInput";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 숫자를 콤마 포맷 문자열로 변환 (입력 중 실시간 표시용)
|
* 숫자 입력 컴포넌트
|
||||||
* 소수점 입력 중인 경우(끝이 "."이거나 ".0" 등)를 보존
|
|
||||||
*/
|
|
||||||
function toCommaDisplay(raw: string): string {
|
|
||||||
if (raw === "" || raw === "-") return raw;
|
|
||||||
const negative = raw.startsWith("-");
|
|
||||||
const abs = negative ? raw.slice(1) : raw;
|
|
||||||
const dotIdx = abs.indexOf(".");
|
|
||||||
const intPart = dotIdx >= 0 ? abs.slice(0, dotIdx) : abs;
|
|
||||||
const decPart = dotIdx >= 0 ? abs.slice(dotIdx) : "";
|
|
||||||
const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
||||||
return (negative ? "-" : "") + formatted + decPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 숫자 입력 컴포넌트 - 입력 중에도 실시간 천단위 콤마 표시
|
|
||||||
*/
|
*/
|
||||||
const NumberInput = forwardRef<
|
const NumberInput = forwardRef<
|
||||||
HTMLInputElement,
|
HTMLInputElement,
|
||||||
|
|
@ -266,112 +250,40 @@ const NumberInput = forwardRef<
|
||||||
className?: string;
|
className?: string;
|
||||||
inputStyle?: React.CSSProperties;
|
inputStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
>(({ value, onChange, min, max, placeholder, readonly, disabled, className, inputStyle }, ref) => {
|
>(({ value, onChange, min, max, step = 1, placeholder, readonly, disabled, className, inputStyle }, ref) => {
|
||||||
const innerRef = useRef<HTMLInputElement>(null);
|
|
||||||
const combinedRef = (node: HTMLInputElement | null) => {
|
|
||||||
(innerRef as React.MutableRefObject<HTMLInputElement | null>).current = node;
|
|
||||||
if (typeof ref === "function") ref(node);
|
|
||||||
else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 콤마 포함된 표시 문자열을 내부 상태로 관리
|
|
||||||
const [displayValue, setDisplayValue] = useState(() => {
|
|
||||||
if (value === undefined || value === null) return "";
|
|
||||||
return centralFormatNumber(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 외부 value가 변경되면 표시 값 동기화 (포커스 아닐 때만)
|
|
||||||
const isFocusedRef = useRef(false);
|
|
||||||
useEffect(() => {
|
|
||||||
if (isFocusedRef.current) return;
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
setDisplayValue("");
|
|
||||||
} else {
|
|
||||||
setDisplayValue(centralFormatNumber(value));
|
|
||||||
}
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const input = e.target;
|
const val = e.target.value;
|
||||||
const cursorPos = input.selectionStart ?? 0;
|
if (val === "") {
|
||||||
const oldVal = displayValue;
|
|
||||||
const rawInput = e.target.value;
|
|
||||||
|
|
||||||
// 콤마 제거하여 순수 숫자 문자열 추출
|
|
||||||
const stripped = rawInput.replace(/,/g, "");
|
|
||||||
|
|
||||||
// 빈 값 처리
|
|
||||||
if (stripped === "" || stripped === "-") {
|
|
||||||
setDisplayValue(stripped);
|
|
||||||
onChange?.(undefined);
|
onChange?.(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 숫자 + 소수점만 허용 (입력 중 "123." 같은 중간 상태도 허용)
|
let num = parseFloat(val);
|
||||||
if (!/^-?\d*\.?\d*$/.test(stripped)) return;
|
|
||||||
|
|
||||||
// 새 콤마 포맷 생성
|
|
||||||
const newDisplay = toCommaDisplay(stripped);
|
|
||||||
setDisplayValue(newDisplay);
|
|
||||||
|
|
||||||
// 콤마 개수 차이로 커서 위치 보정
|
|
||||||
const oldCommas = (oldVal.slice(0, cursorPos).match(/,/g) || []).length;
|
|
||||||
const newCommas = (newDisplay.slice(0, cursorPos).match(/,/g) || []).length;
|
|
||||||
const adjustedCursor = cursorPos + (newCommas - oldCommas);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (innerRef.current) {
|
|
||||||
innerRef.current.setSelectionRange(adjustedCursor, adjustedCursor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 실제 숫자 값 전달 (소수점 입력 중이면 아직 전달하지 않음)
|
|
||||||
if (stripped.endsWith(".") || stripped.endsWith("-")) return;
|
|
||||||
let num = parseFloat(stripped);
|
|
||||||
if (isNaN(num)) return;
|
|
||||||
|
|
||||||
|
// 범위 제한
|
||||||
if (min !== undefined && num < min) num = min;
|
if (min !== undefined && num < min) num = min;
|
||||||
if (max !== undefined && num > max) num = max;
|
if (max !== undefined && num > max) num = max;
|
||||||
|
|
||||||
onChange?.(num);
|
onChange?.(num);
|
||||||
},
|
},
|
||||||
[min, max, onChange, displayValue],
|
[min, max, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
|
||||||
isFocusedRef.current = true;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
|
||||||
isFocusedRef.current = false;
|
|
||||||
// 블러 시 최종 포맷 정리
|
|
||||||
const stripped = displayValue.replace(/,/g, "");
|
|
||||||
if (stripped === "" || stripped === "-" || stripped === ".") {
|
|
||||||
setDisplayValue("");
|
|
||||||
onChange?.(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const num = parseFloat(stripped);
|
|
||||||
if (!isNaN(num)) {
|
|
||||||
setDisplayValue(centralFormatNumber(num));
|
|
||||||
}
|
|
||||||
}, [displayValue, onChange]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
ref={combinedRef}
|
ref={ref}
|
||||||
type="text"
|
type="number"
|
||||||
inputMode="decimal"
|
value={value ?? ""}
|
||||||
value={displayValue}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onFocus={handleFocus}
|
min={min}
|
||||||
onBlur={handleBlur}
|
max={max}
|
||||||
|
step={step}
|
||||||
placeholder={placeholder || "숫자 입력"}
|
placeholder={placeholder || "숫자 입력"}
|
||||||
readOnly={readonly}
|
readOnly={readonly}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn("h-full w-full", className)}
|
className={cn("h-full w-full", className)}
|
||||||
style={{ ...inputStyle, textAlign: "right" }}
|
style={inputStyle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -63,23 +63,10 @@ function applyDateFormat(date: Date, pattern: string): string {
|
||||||
|
|
||||||
// --- 숫자 포맷 ---
|
// --- 숫자 포맷 ---
|
||||||
|
|
||||||
/** 최대 허용 소수점 자릿수 */
|
|
||||||
const MAX_DECIMAL_PLACES = 5;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 실제 값에서 소수점 자릿수를 감지한다 (최대 MAX_DECIMAL_PLACES).
|
* 숫자를 로케일 기반으로 포맷한다 (천단위 구분자 등).
|
||||||
*/
|
|
||||||
function detectDecimals(num: number): number {
|
|
||||||
if (Number.isInteger(num)) return 0;
|
|
||||||
const parts = String(num).split(".");
|
|
||||||
if (parts.length < 2) return 0;
|
|
||||||
return Math.min(parts[1].length, MAX_DECIMAL_PLACES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 숫자를 로케일 기반으로 포맷한다 (천단위 구분자 + 소수점 자동감지).
|
|
||||||
* @param value - 숫자 또는 숫자 문자열
|
* @param value - 숫자 또는 숫자 문자열
|
||||||
* @param decimals - 소수점 자릿수 (미지정 시 실제 값에서 자동감지, 최대 5자리)
|
* @param decimals - 소수점 자릿수 (미지정 시 기본값 사용)
|
||||||
* @returns 포맷된 문자열
|
* @returns 포맷된 문자열
|
||||||
*/
|
*/
|
||||||
export function formatNumber(value: unknown, decimals?: number): string {
|
export function formatNumber(value: unknown, decimals?: number): string {
|
||||||
|
|
@ -89,7 +76,7 @@ export function formatNumber(value: unknown, decimals?: number): string {
|
||||||
const num = typeof value === "number" ? value : parseFloat(String(value));
|
const num = typeof value === "number" ? value : parseFloat(String(value));
|
||||||
if (isNaN(num)) return String(value);
|
if (isNaN(num)) return String(value);
|
||||||
|
|
||||||
const dec = decimals ?? detectDecimals(num);
|
const dec = decimals ?? rules.number.decimals;
|
||||||
|
|
||||||
return new Intl.NumberFormat(rules.number.locale, {
|
return new Intl.NumberFormat(rules.number.locale, {
|
||||||
minimumFractionDigits: dec,
|
minimumFractionDigits: dec,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { RepeaterColumnConfig } from "./types";
|
import { RepeaterColumnConfig } from "./types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
|
||||||
|
|
||||||
// @dnd-kit imports
|
// @dnd-kit imports
|
||||||
import {
|
import {
|
||||||
|
|
@ -595,10 +594,21 @@ export function RepeaterTable({
|
||||||
|
|
||||||
// 계산 필드는 편집 불가
|
// 계산 필드는 편집 불가
|
||||||
if (column.calculated || !column.editable) {
|
if (column.calculated || !column.editable) {
|
||||||
|
// 숫자 포맷팅 함수: 정수/소수점 자동 구분
|
||||||
|
const formatNumber = (val: any): string => {
|
||||||
|
if (val === undefined || val === null || val === "") return "0";
|
||||||
|
const num = typeof val === "number" ? val : parseFloat(val);
|
||||||
|
if (isNaN(num)) return "0";
|
||||||
|
// 정수면 소수점 없이, 소수면 소수점 유지
|
||||||
|
if (Number.isInteger(num)) {
|
||||||
|
return num.toLocaleString("ko-KR");
|
||||||
|
} else {
|
||||||
|
return num.toLocaleString("ko-KR");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 🆕 카테고리 타입이면 라벨로 변환하여 표시
|
// 🆕 카테고리 타입이면 라벨로 변환하여 표시
|
||||||
const displayValue = column.type === "number"
|
const displayValue = column.type === "number" ? formatNumber(value) : getCategoryDisplayValue(value);
|
||||||
? (value === undefined || value === null || value === "" ? "0" : centralFormatNumber(value) || "0")
|
|
||||||
: getCategoryDisplayValue(value);
|
|
||||||
|
|
||||||
// 🆕 40자 초과 시 ... 처리 및 툴팁
|
// 🆕 40자 초과 시 ... 처리 및 툴팁
|
||||||
const { truncated, isTruncated } = truncateText(String(displayValue));
|
const { truncated, isTruncated } = truncateText(String(displayValue));
|
||||||
|
|
@ -613,28 +623,24 @@ export function RepeaterTable({
|
||||||
// 편집 가능한 필드
|
// 편집 가능한 필드
|
||||||
switch (column.type) {
|
switch (column.type) {
|
||||||
case "number":
|
case "number":
|
||||||
// 콤마 포함 숫자 표시
|
// 숫자 표시: 정수/소수점 자동 구분
|
||||||
const displayValue = (() => {
|
const displayValue = (() => {
|
||||||
if (value === undefined || value === null || value === "") return "";
|
if (value === undefined || value === null || value === "") return "";
|
||||||
const num = typeof value === "number" ? value : parseFloat(String(value));
|
const num = typeof value === "number" ? value : parseFloat(value);
|
||||||
if (isNaN(num)) return "";
|
if (isNaN(num)) return "";
|
||||||
return centralFormatNumber(num);
|
return num.toString();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="decimal"
|
inputMode="numeric"
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const stripped = e.target.value.replace(/,/g, "");
|
const val = e.target.value;
|
||||||
if (stripped === "" || stripped === "-") {
|
// 숫자와 소수점만 허용
|
||||||
handleCellEdit(rowIndex, column.field, 0);
|
if (val === "" || /^-?\d*\.?\d*$/.test(val)) {
|
||||||
return;
|
handleCellEdit(rowIndex, column.field, val === "" ? 0 : parseFloat(val) || 0);
|
||||||
}
|
|
||||||
if (!/^-?\d*\.?\d*$/.test(stripped)) return;
|
|
||||||
if (!stripped.endsWith(".")) {
|
|
||||||
handleCellEdit(rowIndex, column.field, parseFloat(stripped) || 0);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="border-border focus:border-primary focus:ring-ring h-8 w-full min-w-0 rounded-none text-right text-xs focus:ring-1"
|
className="border-border focus:border-primary focus:ring-ring h-8 w-full min-w-0 rounded-none text-right text-xs focus:ring-1"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
|
||||||
import {
|
import {
|
||||||
X,
|
X,
|
||||||
Check,
|
Check,
|
||||||
|
|
@ -1287,7 +1286,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
||||||
// 숫자 포맷팅 헬퍼: 콤마 표시 + 실제 값은 숫자만 저장
|
// 숫자 포맷팅 헬퍼: 콤마 표시 + 실제 값은 숫자만 저장
|
||||||
const rawNum = value ? String(value).replace(/,/g, "") : "";
|
const rawNum = value ? String(value).replace(/,/g, "") : "";
|
||||||
const displayNum = rawNum && !isNaN(Number(rawNum))
|
const displayNum = rawNum && !isNaN(Number(rawNum))
|
||||||
? centralFormatNumber(Number(rawNum))
|
? new Intl.NumberFormat("ko-KR").format(Number(rawNum))
|
||||||
: rawNum;
|
: rawNum;
|
||||||
|
|
||||||
// 계산된 단가는 읽기 전용 + 강조 표시
|
// 계산된 단가는 읽기 전용 + 강조 표시
|
||||||
|
|
@ -1512,9 +1511,9 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
||||||
if (matched) return matched.label;
|
if (matched) return matched.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 숫자는 천 단위 구분 (공통 formatNumber 사용)
|
// 숫자는 천 단위 구분
|
||||||
if (renderType === "number" && !isNaN(Number(strValue))) {
|
if (renderType === "number" && !isNaN(Number(strValue))) {
|
||||||
return centralFormatNumber(Number(strValue));
|
return new Intl.NumberFormat("ko-KR").format(Number(strValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
return strValue;
|
return strValue;
|
||||||
|
|
@ -1647,10 +1646,11 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
||||||
|
|
||||||
switch (displayItem.format) {
|
switch (displayItem.format) {
|
||||||
case "currency":
|
case "currency":
|
||||||
formattedValue = centralFormatNumber(Number(fieldValue) || 0);
|
// 천 단위 구분
|
||||||
|
formattedValue = new Intl.NumberFormat("ko-KR").format(Number(fieldValue) || 0);
|
||||||
break;
|
break;
|
||||||
case "number":
|
case "number":
|
||||||
formattedValue = centralFormatNumber(Number(fieldValue) || 0);
|
formattedValue = new Intl.NumberFormat("ko-KR").format(Number(fieldValue) || 0);
|
||||||
break;
|
break;
|
||||||
case "date":
|
case "date":
|
||||||
// YYYY.MM.DD 형식
|
// YYYY.MM.DD 형식
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { ComponentRendererProps } from "@/types/component";
|
||||||
import { useCalculation } from "./useCalculation";
|
import { useCalculation } from "./useCalculation";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { isColumnRequiredByMeta } from "@/lib/registry/DynamicComponentRenderer";
|
import { isColumnRequiredByMeta } from "@/lib/registry/DynamicComponentRenderer";
|
||||||
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
|
||||||
|
|
||||||
export interface SimpleRepeaterTableComponentProps extends ComponentRendererProps {
|
export interface SimpleRepeaterTableComponentProps extends ComponentRendererProps {
|
||||||
config?: SimpleRepeaterTableProps;
|
config?: SimpleRepeaterTableProps;
|
||||||
|
|
@ -520,17 +519,18 @@ export function SimpleRepeaterTableComponent({
|
||||||
return result;
|
return result;
|
||||||
}, [value, summaryConfig]);
|
}, [value, summaryConfig]);
|
||||||
|
|
||||||
// 합계 값 포맷팅 (공통 formatNumber 사용)
|
// 합계 값 포맷팅
|
||||||
const formatSummaryValue = (field: SummaryFieldConfig, value: number): string => {
|
const formatSummaryValue = (field: SummaryFieldConfig, value: number): string => {
|
||||||
const decimals = field.decimals ?? 0;
|
const decimals = field.decimals ?? 0;
|
||||||
|
const formatted = value.toFixed(decimals);
|
||||||
|
|
||||||
switch (field.format) {
|
switch (field.format) {
|
||||||
case "currency":
|
case "currency":
|
||||||
return centralFormatNumber(value, decimals) + "원";
|
return Number(formatted).toLocaleString() + "원";
|
||||||
case "percent":
|
case "percent":
|
||||||
return value.toFixed(decimals) + "%";
|
return formatted + "%";
|
||||||
default:
|
default:
|
||||||
return centralFormatNumber(value, decimals);
|
return Number(formatted).toLocaleString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -554,9 +554,9 @@ export function SimpleRepeaterTableComponent({
|
||||||
return (
|
return (
|
||||||
<div className="px-2 py-1">
|
<div className="px-2 py-1">
|
||||||
{column.type === "number"
|
{column.type === "number"
|
||||||
? (cellValue !== null && cellValue !== undefined && cellValue !== ""
|
? typeof cellValue === "number"
|
||||||
? centralFormatNumber(cellValue)
|
? cellValue.toLocaleString()
|
||||||
: "0")
|
: cellValue || "0"
|
||||||
: cellValue || "-"}
|
: cellValue || "-"}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -565,28 +565,12 @@ export function SimpleRepeaterTableComponent({
|
||||||
// 편집 가능한 필드
|
// 편집 가능한 필드
|
||||||
switch (column.type) {
|
switch (column.type) {
|
||||||
case "number":
|
case "number":
|
||||||
const numDisplay = (() => {
|
|
||||||
if (cellValue === undefined || cellValue === null || cellValue === "") return "";
|
|
||||||
const n = typeof cellValue === "number" ? cellValue : parseFloat(String(cellValue));
|
|
||||||
return isNaN(n) ? "" : centralFormatNumber(n);
|
|
||||||
})();
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="number"
|
||||||
inputMode="decimal"
|
value={cellValue || ""}
|
||||||
value={numDisplay}
|
onChange={(e) => handleCellEdit(rowIndex, column.field, parseFloat(e.target.value) || 0)}
|
||||||
onChange={(e) => {
|
className="h-7 text-xs"
|
||||||
const stripped = e.target.value.replace(/,/g, "");
|
|
||||||
if (stripped === "" || stripped === "-") {
|
|
||||||
handleCellEdit(rowIndex, column.field, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!/^-?\d*\.?\d*$/.test(stripped)) return;
|
|
||||||
if (!stripped.endsWith(".")) {
|
|
||||||
handleCellEdit(rowIndex, column.field, parseFloat(stripped) || 0);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="h-7 text-right text-xs"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -379,12 +379,33 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
}, [tableConfig.selectedTable, currentUserId]);
|
}, [tableConfig.selectedTable, currentUserId]);
|
||||||
|
|
||||||
// columnVisibility 변경 시 컬럼 순서 및 가시성 적용
|
// columnVisibility 변경 시 컬럼 순서, 가시성, 너비 적용
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (columnVisibility.length > 0) {
|
if (columnVisibility.length > 0) {
|
||||||
const newOrder = columnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // 체크박스 제외
|
const newOrder = columnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // 체크박스 제외
|
||||||
setColumnOrder(newOrder);
|
setColumnOrder(newOrder);
|
||||||
|
|
||||||
|
// 너비 적용
|
||||||
|
const newWidths: Record<string, number> = {};
|
||||||
|
columnVisibility.forEach((cv) => {
|
||||||
|
if (cv.width) {
|
||||||
|
newWidths[cv.columnName] = cv.width;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(newWidths).length > 0) {
|
||||||
|
setColumnWidths((prev) => ({ ...prev, ...newWidths }));
|
||||||
|
|
||||||
|
// table_column_widths_* localStorage도 동기화 (초기 너비 로드 시 올바른 값 사용)
|
||||||
|
if (tableConfig.selectedTable && userId) {
|
||||||
|
const widthsKey = `table_column_widths_${tableConfig.selectedTable}_${userId}`;
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(widthsKey);
|
||||||
|
const merged = existing ? { ...JSON.parse(existing), ...newWidths } : newWidths;
|
||||||
|
localStorage.setItem(widthsKey, JSON.stringify(merged));
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// localStorage에 저장 (사용자별)
|
// localStorage에 저장 (사용자별)
|
||||||
if (tableConfig.selectedTable && currentUserId) {
|
if (tableConfig.selectedTable && currentUserId) {
|
||||||
const storageKey = `table_column_visibility_${tableConfig.selectedTable}_${currentUserId}`;
|
const storageKey = `table_column_visibility_${tableConfig.selectedTable}_${currentUserId}`;
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
...restComponentStyle,
|
...restComponentStyle,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
borderRadius: _br || "0.5rem",
|
||||||
|
overflow: "hidden",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 디자인 모드 스타일
|
// 디자인 모드 스타일
|
||||||
|
|
|
||||||
|
|
@ -17,37 +17,25 @@ interface ProcessWorkStandardComponentProps {
|
||||||
formData?: Record<string, any>;
|
formData?: Record<string, any>;
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
tableName?: string;
|
tableName?: string;
|
||||||
screenId?: number | string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProcessWorkStandardComponent({
|
export function ProcessWorkStandardComponent({
|
||||||
config: configProp,
|
config: configProp,
|
||||||
isPreview,
|
isPreview,
|
||||||
screenId,
|
|
||||||
}: ProcessWorkStandardComponentProps) {
|
}: ProcessWorkStandardComponentProps) {
|
||||||
const resolvedConfig = useMemo(() => {
|
|
||||||
const merged = {
|
|
||||||
...configProp,
|
|
||||||
};
|
|
||||||
if (merged.itemListMode === "registered" && !merged.screenCode && screenId) {
|
|
||||||
merged.screenCode = `screen_${screenId}`;
|
|
||||||
}
|
|
||||||
return merged;
|
|
||||||
}, [configProp, screenId]);
|
|
||||||
|
|
||||||
const config: ProcessWorkStandardConfig = useMemo(
|
const config: ProcessWorkStandardConfig = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...resolvedConfig,
|
...configProp,
|
||||||
dataSource: { ...defaultConfig.dataSource, ...resolvedConfig?.dataSource },
|
dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource },
|
||||||
phases: resolvedConfig?.phases?.length
|
phases: configProp?.phases?.length
|
||||||
? resolvedConfig.phases
|
? configProp.phases
|
||||||
: defaultConfig.phases,
|
: defaultConfig.phases,
|
||||||
detailTypes: resolvedConfig?.detailTypes?.length
|
detailTypes: configProp?.detailTypes?.length
|
||||||
? resolvedConfig.detailTypes
|
? configProp.detailTypes
|
||||||
: defaultConfig.detailTypes,
|
: defaultConfig.detailTypes,
|
||||||
}),
|
}),
|
||||||
[resolvedConfig]
|
[configProp]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -58,8 +46,7 @@ export function ProcessWorkStandardComponent({
|
||||||
selectedDetailsByPhase,
|
selectedDetailsByPhase,
|
||||||
selection,
|
selection,
|
||||||
loading,
|
loading,
|
||||||
isRegisteredMode,
|
fetchItems,
|
||||||
loadItems,
|
|
||||||
selectItem,
|
selectItem,
|
||||||
selectProcess,
|
selectProcess,
|
||||||
fetchWorkItemDetails,
|
fetchWorkItemDetails,
|
||||||
|
|
@ -125,8 +112,8 @@ export function ProcessWorkStandardComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInit = useCallback(() => {
|
const handleInit = useCallback(() => {
|
||||||
loadItems();
|
fetchItems();
|
||||||
}, [loadItems]);
|
}, [fetchItems]);
|
||||||
|
|
||||||
const splitRatio = config.splitRatio || 30;
|
const splitRatio = config.splitRatio || 30;
|
||||||
|
|
||||||
|
|
@ -157,7 +144,7 @@ export function ProcessWorkStandardComponent({
|
||||||
items={items}
|
items={items}
|
||||||
routings={routings}
|
routings={routings}
|
||||||
selection={selection}
|
selection={selection}
|
||||||
onSearch={(keyword) => loadItems(keyword)}
|
onSearch={(keyword) => fetchItems(keyword)}
|
||||||
onSelectItem={selectItem}
|
onSelectItem={selectItem}
|
||||||
onSelectProcess={selectProcess}
|
onSelectProcess={selectProcess}
|
||||||
onInit={handleInit}
|
onInit={handleInit}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
||||||
import { ProcessWorkStandardConfig, WorkPhaseDefinition, DetailTypeDefinition } from "./types";
|
import { ProcessWorkStandardConfig, WorkPhaseDefinition, DetailTypeDefinition } from "./types";
|
||||||
import { defaultConfig } from "./config";
|
import { defaultConfig } from "./config";
|
||||||
|
|
||||||
|
|
@ -82,30 +81,6 @@ export function ProcessWorkStandardConfigPanel({
|
||||||
<div className="space-y-5 p-4">
|
<div className="space-y-5 p-4">
|
||||||
<h3 className="text-sm font-semibold">공정 작업기준 설정</h3>
|
<h3 className="text-sm font-semibold">공정 작업기준 설정</h3>
|
||||||
|
|
||||||
{/* 품목 목록 모드 */}
|
|
||||||
<section className="space-y-3">
|
|
||||||
<p className="text-xs font-medium text-muted-foreground">품목 목록 모드</p>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
value={config.itemListMode || "all"}
|
|
||||||
onValueChange={(v) => update({ itemListMode: v as "all" | "registered" })}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">전체 품목</SelectItem>
|
|
||||||
<SelectItem value="registered">등록 품목만</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
|
||||||
{config.itemListMode === "registered"
|
|
||||||
? "품목별 라우팅 탭에서 등록한 품목만 표시됩니다. screenCode는 화면 ID 기준으로 자동 설정됩니다."
|
|
||||||
: "모든 품목을 표시합니다."}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* 데이터 소스 설정 */}
|
{/* 데이터 소스 설정 */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<p className="text-xs font-medium text-muted-foreground">데이터 소스 설정</p>
|
<p className="text-xs font-medium text-muted-foreground">데이터 소스 설정</p>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere
|
||||||
static componentDefinition = V2ProcessWorkStandardDefinition;
|
static componentDefinition = V2ProcessWorkStandardDefinition;
|
||||||
|
|
||||||
render(): React.ReactElement {
|
render(): React.ReactElement {
|
||||||
const { formData, isPreview, config, tableName, screenId } = this.props as Record<
|
const { formData, isPreview, config, tableName } = this.props as Record<
|
||||||
string,
|
string,
|
||||||
unknown
|
unknown
|
||||||
>;
|
>;
|
||||||
|
|
@ -20,7 +20,6 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere
|
||||||
formData={formData as Record<string, unknown>}
|
formData={formData as Record<string, unknown>}
|
||||||
tableName={tableName as string}
|
tableName={tableName as string}
|
||||||
isPreview={isPreview as boolean}
|
isPreview={isPreview as boolean}
|
||||||
screenId={screenId as number | string}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,4 @@ export const defaultConfig: ProcessWorkStandardConfig = {
|
||||||
splitRatio: 30,
|
splitRatio: 30,
|
||||||
leftPanelTitle: "품목 및 공정 선택",
|
leftPanelTitle: "품목 및 공정 선택",
|
||||||
readonly: false,
|
readonly: false,
|
||||||
itemListMode: "all",
|
|
||||||
screenCode: "",
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,7 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
processName: null,
|
processName: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isRegisteredMode = config.itemListMode === "registered";
|
// 품목 목록 조회
|
||||||
|
|
||||||
// 품목 목록 조회 (전체 모드)
|
|
||||||
const fetchItems = useCallback(
|
const fetchItems = useCallback(
|
||||||
async (search?: string) => {
|
async (search?: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -61,53 +59,6 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
[config.dataSource]
|
[config.dataSource]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 등록 품목 조회 (등록 모드)
|
|
||||||
const fetchRegisteredItems = useCallback(
|
|
||||||
async (search?: string) => {
|
|
||||||
const screenCode = config.screenCode;
|
|
||||||
if (!screenCode) {
|
|
||||||
console.warn("screenCode가 설정되지 않았습니다");
|
|
||||||
setItems([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const ds = config.dataSource;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
tableName: ds.itemTable,
|
|
||||||
nameColumn: ds.itemNameColumn,
|
|
||||||
codeColumn: ds.itemCodeColumn,
|
|
||||||
routingTable: ds.routingVersionTable,
|
|
||||||
routingFkColumn: ds.routingFkColumn,
|
|
||||||
...(search ? { search } : {}),
|
|
||||||
});
|
|
||||||
const res = await apiClient.get(
|
|
||||||
`${API_BASE}/registered-items/${encodeURIComponent(screenCode)}?${params}`
|
|
||||||
);
|
|
||||||
if (res.data?.success) {
|
|
||||||
setItems(res.data.data || []);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("등록 품목 조회 실패", err);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[config.dataSource, config.screenCode]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 모드에 따라 적절한 함수 호출
|
|
||||||
const loadItems = useCallback(
|
|
||||||
async (search?: string) => {
|
|
||||||
if (isRegisteredMode) {
|
|
||||||
await fetchRegisteredItems(search);
|
|
||||||
} else {
|
|
||||||
await fetchItems(search);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isRegisteredMode, fetchItems, fetchRegisteredItems]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 라우팅 + 공정 조회
|
// 라우팅 + 공정 조회
|
||||||
const fetchRoutings = useCallback(
|
const fetchRoutings = useCallback(
|
||||||
async (itemCode: string) => {
|
async (itemCode: string) => {
|
||||||
|
|
@ -389,10 +340,7 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
selection,
|
selection,
|
||||||
loading,
|
loading,
|
||||||
saving,
|
saving,
|
||||||
isRegisteredMode,
|
|
||||||
fetchItems,
|
fetchItems,
|
||||||
fetchRegisteredItems,
|
|
||||||
loadItems,
|
|
||||||
selectItem,
|
selectItem,
|
||||||
selectProcess,
|
selectProcess,
|
||||||
fetchWorkItems,
|
fetchWorkItems,
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,6 @@ export interface ProcessWorkStandardConfig {
|
||||||
splitRatio?: number;
|
splitRatio?: number;
|
||||||
leftPanelTitle?: string;
|
leftPanelTitle?: string;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
/** 품목 목록 모드: all=전체, registered=등록된 품목만 */
|
|
||||||
itemListMode?: "all" | "registered";
|
|
||||||
/** 등록 모드 시 화면 코드 (자동 설정됨) */
|
|
||||||
screenCode?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -125,7 +121,6 @@ export interface ProcessWorkStandardComponentProps {
|
||||||
formData?: Record<string, any>;
|
formData?: Record<string, any>;
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
tableName?: string;
|
tableName?: string;
|
||||||
screenId?: number | string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 선택 상태
|
// 선택 상태
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { dataApi } from "@/lib/api/data";
|
import { dataApi } from "@/lib/api/data";
|
||||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||||
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import { apiClient, getFullImageUrl } from "@/lib/api/client";
|
import { apiClient, getFullImageUrl } from "@/lib/api/client";
|
||||||
|
|
@ -1007,20 +1006,19 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
.replace("ss", String(date.getSeconds()).padStart(2, "0"));
|
.replace("ss", String(date.getSeconds()).padStart(2, "0"));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 숫자 포맷팅 헬퍼 함수 (공통 formatNumber 기반)
|
// 숫자 포맷팅 헬퍼 함수
|
||||||
const formatNumberValue = useCallback((value: any, format: any): string => {
|
const formatNumberValue = useCallback((value: any, format: any): string => {
|
||||||
if (value === null || value === undefined || value === "") return "-";
|
if (value === null || value === undefined || value === "") return "-";
|
||||||
const num = typeof value === "number" ? value : parseFloat(String(value));
|
const num = typeof value === "number" ? value : parseFloat(String(value));
|
||||||
if (isNaN(num)) return String(value);
|
if (isNaN(num)) return String(value);
|
||||||
|
|
||||||
let result: string;
|
const options: Intl.NumberFormatOptions = {
|
||||||
if (format?.thousandSeparator === false) {
|
minimumFractionDigits: format?.decimalPlaces ?? 0,
|
||||||
const dec = format?.decimalPlaces ?? 0;
|
maximumFractionDigits: format?.decimalPlaces ?? 10,
|
||||||
result = num.toFixed(dec);
|
useGrouping: format?.thousandSeparator ?? false,
|
||||||
} else {
|
};
|
||||||
result = centralFormatNumber(num, format?.decimalPlaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let result = num.toLocaleString("ko-KR", options);
|
||||||
if (format?.prefix) result = format.prefix + result;
|
if (format?.prefix) result = format.prefix + result;
|
||||||
if (format?.suffix) result = result + format.suffix;
|
if (format?.suffix) result = result + format.suffix;
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -1090,16 +1088,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD");
|
return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 숫자 포맷 적용 (format 설정이 있거나 input_type이 number/decimal이면 자동 적용)
|
// 🆕 숫자 포맷 적용
|
||||||
const isNumericByInputType = colInputType === "number" || colInputType === "decimal";
|
|
||||||
if (
|
if (
|
||||||
format?.type === "number" ||
|
format?.type === "number" ||
|
||||||
format?.type === "currency" ||
|
format?.type === "currency" ||
|
||||||
format?.thousandSeparator ||
|
format?.thousandSeparator ||
|
||||||
format?.decimalPlaces !== undefined ||
|
format?.decimalPlaces !== undefined
|
||||||
isNumericByInputType
|
|
||||||
) {
|
) {
|
||||||
return formatNumberValue(value, format || { thousandSeparator: true });
|
return formatNumberValue(value, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 카테고리 매핑 찾기 (여러 키 형태 시도)
|
// 카테고리 매핑 찾기 (여러 키 형태 시도)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core";
|
import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core";
|
||||||
import { getAdaptiveLabelColor } from "@/lib/utils/darkModeColor";
|
import { getAdaptiveLabelColor } from "@/lib/utils/darkModeColor";
|
||||||
import { useTabId } from "@/contexts/TabIdContext";
|
import { useTabId } from "@/contexts/TabIdContext";
|
||||||
import { formatNumber as centralFormatNumber, formatCurrency as centralFormatCurrency } from "@/lib/formatting";
|
|
||||||
|
|
||||||
// 🖼️ 테이블 셀 이미지 썸네일 컴포넌트
|
// 🖼️ 테이블 셀 이미지 썸네일 컴포넌트
|
||||||
// objid인 경우 인증된 API로 blob URL 생성, 경로인 경우 직접 URL 사용
|
// objid인 경우 인증된 API로 blob URL 생성, 경로인 경우 직접 URL 사용
|
||||||
|
|
@ -521,12 +520,33 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
}, [tableConfig.selectedTable, currentUserId]);
|
}, [tableConfig.selectedTable, currentUserId]);
|
||||||
|
|
||||||
// columnVisibility 변경 시 컬럼 순서 및 가시성 적용
|
// columnVisibility 변경 시 컬럼 순서, 가시성, 너비 적용
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (columnVisibility.length > 0) {
|
if (columnVisibility.length > 0) {
|
||||||
const newOrder = columnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // 체크박스 제외
|
const newOrder = columnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // 체크박스 제외
|
||||||
setColumnOrder(newOrder);
|
setColumnOrder(newOrder);
|
||||||
|
|
||||||
|
// 너비 적용
|
||||||
|
const newWidths: Record<string, number> = {};
|
||||||
|
columnVisibility.forEach((cv) => {
|
||||||
|
if (cv.width) {
|
||||||
|
newWidths[cv.columnName] = cv.width;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(newWidths).length > 0) {
|
||||||
|
setColumnWidths((prev) => ({ ...prev, ...newWidths }));
|
||||||
|
|
||||||
|
// table_column_widths_* localStorage도 동기화 (초기 너비 로드 시 올바른 값 사용)
|
||||||
|
if (tableConfig.selectedTable && userId) {
|
||||||
|
const widthsKey = `table_column_widths_${tableConfig.selectedTable}_${userId}`;
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(widthsKey);
|
||||||
|
const merged = existing ? { ...JSON.parse(existing), ...newWidths } : newWidths;
|
||||||
|
localStorage.setItem(widthsKey, JSON.stringify(merged));
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// localStorage에 저장 (사용자별)
|
// localStorage에 저장 (사용자별)
|
||||||
if (tableConfig.selectedTable && currentUserId) {
|
if (tableConfig.selectedTable && currentUserId) {
|
||||||
const storageKey = `table_column_visibility_${tableConfig.selectedTable}_${currentUserId}`;
|
const storageKey = `table_column_visibility_${tableConfig.selectedTable}_${currentUserId}`;
|
||||||
|
|
@ -4446,14 +4466,17 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 숫자 타입 포맷팅 (공통 formatNumber 사용)
|
// 숫자 타입 포맷팅 (천단위 구분자 설정 확인)
|
||||||
if (inputType === "number" || inputType === "decimal") {
|
if (inputType === "number" || inputType === "decimal") {
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
if (column.thousandSeparator !== false) {
|
|
||||||
return centralFormatNumber(value);
|
|
||||||
}
|
|
||||||
const numValue = typeof value === "string" ? parseFloat(value) : value;
|
const numValue = typeof value === "string" ? parseFloat(value) : value;
|
||||||
return isNaN(numValue) ? String(value) : String(numValue);
|
if (!isNaN(numValue)) {
|
||||||
|
// thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용
|
||||||
|
if (column.thousandSeparator !== false) {
|
||||||
|
return numValue.toLocaleString("ko-KR");
|
||||||
|
}
|
||||||
|
return String(numValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
@ -4461,11 +4484,14 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
switch (column.format) {
|
switch (column.format) {
|
||||||
case "number":
|
case "number":
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
if (column.thousandSeparator !== false) {
|
|
||||||
return centralFormatNumber(value);
|
|
||||||
}
|
|
||||||
const numValue = typeof value === "string" ? parseFloat(value) : value;
|
const numValue = typeof value === "string" ? parseFloat(value) : value;
|
||||||
return isNaN(numValue) ? String(value) : String(numValue);
|
if (!isNaN(numValue)) {
|
||||||
|
// thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용
|
||||||
|
if (column.thousandSeparator !== false) {
|
||||||
|
return numValue.toLocaleString("ko-KR");
|
||||||
|
}
|
||||||
|
return String(numValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return String(value);
|
return String(value);
|
||||||
case "date":
|
case "date":
|
||||||
|
|
@ -4482,8 +4508,12 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
return "-";
|
return "-";
|
||||||
case "currency":
|
case "currency":
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (typeof value === "number") {
|
||||||
return centralFormatCurrency(value);
|
// thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용
|
||||||
|
if (column.thousandSeparator !== false) {
|
||||||
|
return `₩${value.toLocaleString()}`;
|
||||||
|
}
|
||||||
|
return `₩${value}`;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue