feat: Add process work standard component implementation plan
- Introduced a comprehensive implementation plan for the v2-process-work-standard component, detailing the current state analysis, required database tables, API design, and implementation phases. - Included a structured file organization plan for both frontend and backend components, ensuring clarity in development and integration. - Updated the V2Repeater component to support new auto-fill functionalities, including parent sequence generation, enhancing data management capabilities. - Enhanced the V2RepeaterConfigPanel to allow configuration of parent sequence settings, improving user experience in managing data entries.
This commit is contained in:
parent
9cc93b88ff
commit
e8c0828d91
|
|
@ -0,0 +1,427 @@
|
||||||
|
# 공정 작업기준 컴포넌트 (v2-process-work-standard) 구현 계획
|
||||||
|
|
||||||
|
> **작성일**: 2026-02-24
|
||||||
|
> **컴포넌트 ID**: `v2-process-work-standard`
|
||||||
|
> **성격**: 도메인 특화 컴포넌트 (v2-rack-structure와 동일 패턴)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 현황 분석
|
||||||
|
|
||||||
|
### 1.1 기존 DB 테이블 (참조용, 이미 존재)
|
||||||
|
|
||||||
|
| 테이블 | 역할 | 핵심 컬럼 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| `item_info` | 품목 마스터 | id, item_name, item_number, company_code |
|
||||||
|
| `item_routing_version` | 라우팅 버전 | id, item_code, version_name, company_code |
|
||||||
|
| `item_routing_detail` | 라우팅 상세 (공정 배정) | id, routing_version_id, seq_no, process_code, company_code |
|
||||||
|
| `process_mng` | 공정 마스터 | id, process_code, process_name, company_code |
|
||||||
|
|
||||||
|
### 1.2 신규 생성 필요 테이블
|
||||||
|
|
||||||
|
**`process_work_item`** - 작업 항목 (검사 장비 준비, 외관 검사 등)
|
||||||
|
|
||||||
|
| 컬럼 | 타입 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | VARCHAR PK | UUID |
|
||||||
|
| company_code | VARCHAR NOT NULL | 멀티테넌시 |
|
||||||
|
| routing_detail_id | VARCHAR NOT NULL | item_routing_detail.id FK |
|
||||||
|
| work_phase | VARCHAR NOT NULL | Config의 phases[].key 값 (예: 'PRE', 'IN', 'POST' 또는 사용자 정의) |
|
||||||
|
| title | VARCHAR NOT NULL | 항목 제목 (예: 검사 장비 준비) |
|
||||||
|
| is_required | VARCHAR | 'Y' / 'N' |
|
||||||
|
| sort_order | INTEGER | 표시 순서 |
|
||||||
|
| description | TEXT | 비고/설명 |
|
||||||
|
| created_date | TIMESTAMP | 생성일 |
|
||||||
|
| updated_date | TIMESTAMP | 수정일 |
|
||||||
|
| writer | VARCHAR | 작성자 |
|
||||||
|
|
||||||
|
**`process_work_item_detail`** - 작업 항목 상세 (버니어 캘리퍼스 상태 소정 등)
|
||||||
|
|
||||||
|
| 컬럼 | 타입 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | VARCHAR PK | UUID |
|
||||||
|
| company_code | VARCHAR NOT NULL | 멀티테넌시 |
|
||||||
|
| work_item_id | VARCHAR NOT NULL | process_work_item.id FK |
|
||||||
|
| detail_type | VARCHAR | 'CHECK' / 'INSPECTION' / 'MEASUREMENT' 등 |
|
||||||
|
| content | VARCHAR NOT NULL | 상세 내용 |
|
||||||
|
| is_required | VARCHAR | 'Y' / 'N' |
|
||||||
|
| sort_order | INTEGER | 표시 순서 |
|
||||||
|
| remark | TEXT | 비고 |
|
||||||
|
| created_date | TIMESTAMP | 생성일 |
|
||||||
|
| updated_date | TIMESTAMP | 수정일 |
|
||||||
|
| writer | VARCHAR | 작성자 |
|
||||||
|
|
||||||
|
### 1.3 데이터 흐름 (5단계 연쇄)
|
||||||
|
|
||||||
|
```
|
||||||
|
item_info (품목)
|
||||||
|
└─→ item_routing_version (라우팅 버전)
|
||||||
|
└─→ item_routing_detail (공정 배정) ← JOIN → process_mng (공정명)
|
||||||
|
└─→ process_work_item (작업 항목, phase별)
|
||||||
|
└─→ process_work_item_detail (상세)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 파일 구조 계획
|
||||||
|
|
||||||
|
### 2.1 프론트엔드 (컴포넌트 등록)
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/lib/registry/components/v2-process-work-standard/
|
||||||
|
├── index.ts # createComponentDefinition
|
||||||
|
├── types.ts # 타입 정의
|
||||||
|
├── config.ts # 기본 설정
|
||||||
|
├── ProcessWorkStandardRenderer.tsx # AutoRegisteringComponentRenderer
|
||||||
|
├── ProcessWorkStandardConfigPanel.tsx # 설정 패널
|
||||||
|
├── ProcessWorkStandardComponent.tsx # 메인 UI (좌우 분할)
|
||||||
|
├── components/
|
||||||
|
│ ├── ItemProcessSelector.tsx # 좌측: 품목/라우팅/공정 아코디언 트리
|
||||||
|
│ ├── WorkStandardEditor.tsx # 우측: 작업기준 편집 영역 전체
|
||||||
|
│ ├── WorkPhaseSection.tsx # Pre/In/Post 섹션 (3회 재사용)
|
||||||
|
│ ├── WorkItemCard.tsx # 작업 항목 카드
|
||||||
|
│ ├── WorkItemDetailList.tsx # 상세 리스트
|
||||||
|
│ └── WorkItemAddModal.tsx # 작업 항목 추가/수정 모달
|
||||||
|
├── hooks/
|
||||||
|
│ ├── useProcessWorkStandard.ts # 전체 데이터 관리 훅
|
||||||
|
│ ├── useItemProcessTree.ts # 좌측 트리 데이터 훅
|
||||||
|
│ └── useWorkItems.ts # 작업 항목 CRUD 훅
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 백엔드 (API)
|
||||||
|
|
||||||
|
```
|
||||||
|
backend-node/src/
|
||||||
|
├── routes/processWorkStandardRoutes.ts # 라우트 정의
|
||||||
|
└── controllers/processWorkStandardController.ts # 컨트롤러
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 DB 마이그레이션
|
||||||
|
|
||||||
|
```
|
||||||
|
db/migrations/XXX_create_process_work_standard_tables.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. API 설계
|
||||||
|
|
||||||
|
| Method | Endpoint | 설명 |
|
||||||
|
|--------|----------|------|
|
||||||
|
| GET | `/api/process-work-standard/items` | 품목 목록 (라우팅 있는 품목만) |
|
||||||
|
| GET | `/api/process-work-standard/items/:itemCode/routings` | 품목별 라우팅 버전 + 공정 목록 |
|
||||||
|
| GET | `/api/process-work-standard/routing-detail/:routingDetailId/work-items` | 공정별 작업 항목 목록 (phase별 그룹) |
|
||||||
|
| POST | `/api/process-work-standard/work-items` | 작업 항목 추가 |
|
||||||
|
| PUT | `/api/process-work-standard/work-items/:id` | 작업 항목 수정 |
|
||||||
|
| DELETE | `/api/process-work-standard/work-items/:id` | 작업 항목 삭제 |
|
||||||
|
| GET | `/api/process-work-standard/work-items/:workItemId/details` | 작업 항목 상세 목록 |
|
||||||
|
| POST | `/api/process-work-standard/work-item-details` | 상세 추가 |
|
||||||
|
| PUT | `/api/process-work-standard/work-item-details/:id` | 상세 수정 |
|
||||||
|
| DELETE | `/api/process-work-standard/work-item-details/:id` | 상세 삭제 |
|
||||||
|
| PUT | `/api/process-work-standard/save-all` | 전체 저장 (작업 항목 + 상세 일괄) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 구현 단계 (TDD 기반)
|
||||||
|
|
||||||
|
### Phase 1: DB + API 기반
|
||||||
|
|
||||||
|
- [ ] 1-1. 마이그레이션 SQL 작성 (process_work_item, process_work_item_detail)
|
||||||
|
- [ ] 1-2. 마이그레이션 실행 및 테이블 생성 확인
|
||||||
|
- [ ] 1-3. 백엔드 라우트/컨트롤러 작성 (CRUD API)
|
||||||
|
- [ ] 1-4. API 테스트 (품목 목록, 라우팅 조회, 작업항목 CRUD)
|
||||||
|
|
||||||
|
### Phase 2: 컴포넌트 기본 구조
|
||||||
|
|
||||||
|
- [ ] 2-1. types.ts, config.ts, index.ts 작성 (컴포넌트 정의)
|
||||||
|
- [ ] 2-2. Renderer, ConfigPanel 작성 (V2 시스템 등록)
|
||||||
|
- [ ] 2-3. components/index.ts에 import 추가
|
||||||
|
- [ ] 2-4. getComponentConfigPanel.tsx에 매핑 추가
|
||||||
|
- [ ] 2-5. 화면 디자이너에서 컴포넌트 배치 가능 확인
|
||||||
|
|
||||||
|
### Phase 3: 좌측 패널 (품목/공정 선택)
|
||||||
|
|
||||||
|
- [ ] 3-1. useItemProcessTree 훅 구현 (품목 목록 + 라우팅 조회)
|
||||||
|
- [ ] 3-2. ItemProcessSelector 컴포넌트 (아코디언 + 공정 리스트)
|
||||||
|
- [ ] 3-3. 검색 기능 (품목명/공정명 검색)
|
||||||
|
- [ ] 3-4. 선택 상태 관리 + 우측 패널 연동
|
||||||
|
|
||||||
|
### Phase 4: 우측 패널 (작업기준 편집)
|
||||||
|
|
||||||
|
- [ ] 4-1. WorkStandardEditor 기본 레이아웃 (Pre/In/Post 3단 섹션)
|
||||||
|
- [ ] 4-2. useWorkItems 훅 (작업 항목 + 상세 CRUD)
|
||||||
|
- [ ] 4-3. WorkPhaseSection 컴포넌트 (섹션 헤더 + 카드 영역 + 상세 영역)
|
||||||
|
- [ ] 4-4. WorkItemCard 컴포넌트 (카드 UI + 카운트 배지)
|
||||||
|
- [ ] 4-5. WorkItemDetailList 컴포넌트 (상세 목록 + 인라인 편집)
|
||||||
|
- [ ] 4-6. WorkItemAddModal (작업 항목 추가/수정 모달 + 상세 추가)
|
||||||
|
|
||||||
|
### Phase 5: 통합 + 전체 저장
|
||||||
|
|
||||||
|
- [ ] 5-1. 전체 저장 기능 (변경사항 일괄 저장 API 연동)
|
||||||
|
- [ ] 5-2. 공정 선택 시 데이터 로딩/전환 처리
|
||||||
|
- [ ] 5-3. Empty State 처리 (데이터 없을 때 안내 UI)
|
||||||
|
- [ ] 5-4. 로딩/에러 상태 처리
|
||||||
|
|
||||||
|
### Phase 6: 마무리
|
||||||
|
|
||||||
|
- [ ] 6-1. 멀티테넌시 검증 (company_code 필터링)
|
||||||
|
- [ ] 6-2. 반응형 디자인 점검
|
||||||
|
- [ ] 6-3. README.md 작성
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 핵심 UI 설계
|
||||||
|
|
||||||
|
### 5.1 전체 레이아웃
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ v2-process-work-standard │
|
||||||
|
├────────────────────┬────────────────────────────────────────────────┤
|
||||||
|
│ 품목 및 공정 선택 │ [품목명] - [공정명] [전체 저장] │
|
||||||
|
│ │ │
|
||||||
|
│ [검색 입력] │ ── 작업 전 (Pre-Work) N개 항목 ── [+항목추가] │
|
||||||
|
│ │ ┌────────┐ ┌─────────────────────────────┐ │
|
||||||
|
│ ▼ 볼트 M8x20 │ │카드 │ │ 상세 리스트 (선택 시 표시) │ │
|
||||||
|
│ ★ 기본 라우팅 │ │ │ │ │ │
|
||||||
|
│ ◉ 재단 │ └────────┘ └─────────────────────────────┘ │
|
||||||
|
│ ◉ 검사 ← 선택 │ │
|
||||||
|
│ ★ 버전2 │ ── 작업 중 (In-Work) N개 항목 ── [+항목추가] │
|
||||||
|
│ │ ┌────────┐ ┌────────┐ │
|
||||||
|
│ ▶ 기어 50T │ │카드1 │ │카드2 │ (상세: 우측 표시) │
|
||||||
|
│ ▶ 샤프트 D30 │ └────────┘ └────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ │ ── 작업 후 (Post-Work) N개 항목 ── [+항목추가] │
|
||||||
|
│ │ (동일 구조) │
|
||||||
|
├────────────────────┴────────────────────────────────────────────────┤
|
||||||
|
│ 30% │ 70% │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 WorkPhaseSection 내부 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
── 작업 전 (Pre-Work) 4개 항목 ────────────────── [+ 작업항목 추가]
|
||||||
|
┌──────────────────────────────┬──────────────────────────────────────┐
|
||||||
|
│ 작업 항목 카드 목록 │ 선택된 항목 상세 │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────────────────┐ │ [항목 제목] [+ 상세추가]│
|
||||||
|
│ │ ≡ 검사 장비 준비 ✏️ 🗑 │ │ ─────────────────────────────────── │
|
||||||
|
│ │ 4개 필수 │ │ 순서│유형 │내용 │필수│관리│
|
||||||
|
│ └──────────────────────┘ │ 1 │체크 │버니어 캘리퍼스... │필수│✏️🗑│
|
||||||
|
│ │ 2 │체크 │마이크로미터... │선택│✏️🗑│
|
||||||
|
│ ┌──────────────────────┐ │ 3 │체크 │검사대 청소 │선택│✏️🗑│
|
||||||
|
│ │ ≡ 측정 도구 확인 ✏️ 🗑 │ │ 4 │체크 │검사 기록지 준비 │필수│✏️🗑│
|
||||||
|
│ │ 2개 선택 │ │ │
|
||||||
|
│ └──────────────────────┘ │ │
|
||||||
|
└──────────────────────────────┴──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 작업 항목 추가 모달
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ 작업 항목 추가 ✕ │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ 기본 정보 │
|
||||||
|
│ │
|
||||||
|
│ 항목 제목 * 필수 여부 │
|
||||||
|
│ [ ] [필수 ▼] │
|
||||||
|
│ │
|
||||||
|
│ 비고 │
|
||||||
|
│ [ ] │
|
||||||
|
│ │
|
||||||
|
│ 상세 항목 [+ 상세 추가] │
|
||||||
|
│ ┌───┬──────┬──────────────┬────┬────┐ │
|
||||||
|
│ │순서│유형 │내용 │필수│관리│ │
|
||||||
|
│ ├───┼──────┼──────────────┼────┼────┤ │
|
||||||
|
│ │ 1 │체크 │ │필수│ 🗑 │ │
|
||||||
|
│ └───┴──────┴──────────────┴────┴────┘ │
|
||||||
|
│ │
|
||||||
|
│ [취소] [저장] │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 컴포넌트 Config 설계
|
||||||
|
|
||||||
|
### 6.1 설정 패널 UI 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ 공정 작업기준 설정 │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ── 데이터 소스 설정 ────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ 품목 테이블 │
|
||||||
|
│ [item_info ▼] │
|
||||||
|
│ 품목명 컬럼 품목코드 컬럼 │
|
||||||
|
│ [item_name ▼] [item_number ▼] │
|
||||||
|
│ │
|
||||||
|
│ 라우팅 버전 테이블 │
|
||||||
|
│ [item_routing_version ▼] │
|
||||||
|
│ 품목 연결 컬럼 (FK) │
|
||||||
|
│ [item_code ▼] │
|
||||||
|
│ │
|
||||||
|
│ 라우팅 상세 테이블 │
|
||||||
|
│ [item_routing_detail ▼] │
|
||||||
|
│ │
|
||||||
|
│ 공정 마스터 테이블 │
|
||||||
|
│ [process_mng ▼] │
|
||||||
|
│ │
|
||||||
|
│ ── 작업 단계 설정 ────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ ┌────┬────────────────────┬─────────────┬───┐ │
|
||||||
|
│ │순서│ 단계 키(DB저장용) │ 표시 이름 │관리│ │
|
||||||
|
│ ├────┼────────────────────┼─────────────┼───┤ │
|
||||||
|
│ │ 1 │ PRE │ 작업 전 │ 🗑 │ │
|
||||||
|
│ │ 2 │ IN │ 작업 중 │ 🗑 │ │
|
||||||
|
│ │ 3 │ POST │ 작업 후 │ 🗑 │ │
|
||||||
|
│ └────┴────────────────────┴─────────────┴───┘ │
|
||||||
|
│ [+ 단계 추가] │
|
||||||
|
│ │
|
||||||
|
│ ── 상세 유형 옵션 ────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────┬─────────────┬───┐ │
|
||||||
|
│ │ 유형 값(DB저장용) │ 표시 이름 │관리│ │
|
||||||
|
│ ├────────────────────┼─────────────┼───┤ │
|
||||||
|
│ │ CHECK │ 체크 │ 🗑 │ │
|
||||||
|
│ │ INSPECTION │ 검사 │ 🗑 │ │
|
||||||
|
│ │ MEASUREMENT │ 측정 │ 🗑 │ │
|
||||||
|
│ └────────────────────┴─────────────┴───┘ │
|
||||||
|
│ [+ 유형 추가] │
|
||||||
|
│ │
|
||||||
|
│ ── UI 설정 ────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ 좌우 분할 비율 │
|
||||||
|
│ [30 ] % │
|
||||||
|
│ │
|
||||||
|
│ 좌측 패널 제목 │
|
||||||
|
│ [품목 및 공정 선택 ] │
|
||||||
|
│ │
|
||||||
|
│ 읽기 전용 모드 │
|
||||||
|
│ [ ] 활성화 │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Config 타입 정의
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 작업 단계 정의 (사용자가 추가/삭제/이름변경 가능)
|
||||||
|
interface WorkPhaseDefinition {
|
||||||
|
key: string; // DB 저장용 키 (예: "PRE", "IN", "POST", "QC")
|
||||||
|
label: string; // 화면 표시명 (예: "작업 전 (Pre-Work)")
|
||||||
|
sortOrder: number; // 표시 순서
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상세 유형 정의 (사용자가 추가/삭제 가능)
|
||||||
|
interface DetailTypeDefinition {
|
||||||
|
value: string; // DB 저장용 값 (예: "CHECK")
|
||||||
|
label: string; // 화면 표시명 (예: "체크")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 데이터 소스 설정 (사용자가 테이블 지정 가능)
|
||||||
|
interface DataSourceConfig {
|
||||||
|
// 품목 테이블
|
||||||
|
itemTable: string; // 기본: "item_info"
|
||||||
|
itemNameColumn: string; // 기본: "item_name"
|
||||||
|
itemCodeColumn: string; // 기본: "item_number"
|
||||||
|
|
||||||
|
// 라우팅 버전 테이블
|
||||||
|
routingVersionTable: string; // 기본: "item_routing_version"
|
||||||
|
routingItemFkColumn: string; // 기본: "item_code" (품목과 연결하는 FK)
|
||||||
|
routingVersionNameColumn: string; // 기본: "version_name"
|
||||||
|
|
||||||
|
// 라우팅 상세 테이블
|
||||||
|
routingDetailTable: string; // 기본: "item_routing_detail"
|
||||||
|
|
||||||
|
// 공정 마스터 테이블
|
||||||
|
processTable: string; // 기본: "process_mng"
|
||||||
|
processNameColumn: string; // 기본: "process_name"
|
||||||
|
processCodeColumn: string; // 기본: "process_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 전체 Config
|
||||||
|
interface ProcessWorkStandardConfig {
|
||||||
|
// 데이터 소스 설정
|
||||||
|
dataSource: DataSourceConfig;
|
||||||
|
|
||||||
|
// 작업 단계 정의 (기본 3개, 사용자가 추가/삭제/수정 가능)
|
||||||
|
phases: WorkPhaseDefinition[];
|
||||||
|
// 기본값: [
|
||||||
|
// { key: "PRE", label: "작업 전 (Pre-Work)", sortOrder: 1 },
|
||||||
|
// { key: "IN", label: "작업 중 (In-Work)", sortOrder: 2 },
|
||||||
|
// { key: "POST", label: "작업 후 (Post-Work)", sortOrder: 3 },
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// 상세 유형 옵션 (사용자가 추가/삭제 가능)
|
||||||
|
detailTypes: DetailTypeDefinition[];
|
||||||
|
// 기본값: [
|
||||||
|
// { value: "CHECK", label: "체크" },
|
||||||
|
// { value: "INSPECTION", label: "검사" },
|
||||||
|
// { value: "MEASUREMENT", label: "측정" },
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// UI 설정
|
||||||
|
splitRatio?: number; // 좌우 분할 비율, 기본: 30
|
||||||
|
leftPanelTitle?: string; // 좌측 패널 제목, 기본: "품목 및 공정 선택"
|
||||||
|
readonly?: boolean; // 읽기 전용 모드, 기본: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 커스터마이징 시나리오 예시
|
||||||
|
|
||||||
|
**시나리오 A: 제조업 (기본)**
|
||||||
|
```
|
||||||
|
단계: 작업 전 → 작업 중 → 작업 후
|
||||||
|
유형: 체크, 검사, 측정
|
||||||
|
```
|
||||||
|
|
||||||
|
**시나리오 B: 품질검사 강화 회사**
|
||||||
|
```
|
||||||
|
단계: 준비 → 검사 → 판정 → 기록 → 보관
|
||||||
|
유형: 육안검사, 치수검사, 강도검사, 내구검사, 기능검사
|
||||||
|
```
|
||||||
|
|
||||||
|
**시나리오 C: 단순 2단계 회사**
|
||||||
|
```
|
||||||
|
단계: 사전점검 → 사후점검
|
||||||
|
유형: 확인, 기록
|
||||||
|
```
|
||||||
|
|
||||||
|
**시나리오 D: 다른 테이블 사용 회사**
|
||||||
|
```
|
||||||
|
품목 테이블: product_master (item_info 대신)
|
||||||
|
공정 테이블: operation_mng (process_mng 대신)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 DB 설계 반영 사항
|
||||||
|
|
||||||
|
`work_phase` 컬럼은 고정 ENUM이 아니라 **사용자 정의 키(VARCHAR)** 로 저장합니다.
|
||||||
|
- Config에서 `phases[].key` 로 정의한 값이 DB에 저장됨
|
||||||
|
- 예: "PRE", "IN", "POST" 또는 "PREPARE", "INSPECT", "JUDGE", "RECORD", "STORE"
|
||||||
|
- 회사별 Config에 따라 다른 값이 저장되므로, 조회 시 Config의 phases 정의를 기준으로 섹션을 렌더링
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 등록 체크리스트
|
||||||
|
|
||||||
|
| 항목 | 파일 | 작업 |
|
||||||
|
|------|------|------|
|
||||||
|
| 컴포넌트 정의 | `v2-process-work-standard/index.ts` | createComponentDefinition |
|
||||||
|
| 렌더러 등록 | `v2-process-work-standard/...Renderer.tsx` | registerSelf() |
|
||||||
|
| 컴포넌트 로드 | `components/index.ts` | import 추가 |
|
||||||
|
| 설정 패널 매핑 | `getComponentConfigPanel.tsx` | CONFIG_PANEL_MAP 추가 |
|
||||||
|
| 라우트 등록 | `backend-node/src/app.ts` | router.use() 추가 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 의존성
|
||||||
|
|
||||||
|
- 외부 라이브러리 추가: 없음 (기존 shadcn/ui + Lucide 아이콘만 사용)
|
||||||
|
- 기존 API 재사용: dataRoutes의 범용 CRUD는 사용하지 않고 전용 API 개발
|
||||||
|
- 이유: 5단계 JOIN + phase별 그룹핑 등 범용 API로는 처리 불가
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
> **목적**: 다양한 회사에서 V2 컴포넌트를 활용하여 화면을 개발할 때 참고하는 범용 가이드
|
> **목적**: 다양한 회사에서 V2 컴포넌트를 활용하여 화면을 개발할 때 참고하는 범용 가이드
|
||||||
> **대상**: 화면 설계자, 개발자
|
> **대상**: 화면 설계자, 개발자
|
||||||
> **버전**: 1.0.0
|
> **버전**: 1.1.0
|
||||||
> **작성일**: 2026-01-30
|
> **작성일**: 2026-02-23 (최종 업데이트)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -19,60 +19,63 @@
|
||||||
| 카드 뷰 | 이미지+정보 카드 형태 | 설비정보, 대시보드 |
|
| 카드 뷰 | 이미지+정보 카드 형태 | 설비정보, 대시보드 |
|
||||||
| 피벗 분석 | 다차원 집계 | 매출분석, 재고현황 |
|
| 피벗 분석 | 다차원 집계 | 매출분석, 재고현황 |
|
||||||
| 반복 컨테이너 | 데이터 수만큼 UI 반복 | 주문 상세, 항목 리스트 |
|
| 반복 컨테이너 | 데이터 수만큼 UI 반복 | 주문 상세, 항목 리스트 |
|
||||||
|
| 그룹화 테이블 | 그룹핑 기능 포함 테이블 | 카테고리별 집계, 부서별 현황 |
|
||||||
|
| 타임라인/스케줄 | 시간축 기반 일정 관리 | 생산일정, 작업스케줄 |
|
||||||
|
|
||||||
### 1.2 불가능한 화면 유형 (별도 개발 필요)
|
### 1.2 불가능한 화면 유형 (별도 개발 필요)
|
||||||
|
|
||||||
| 화면 유형 | 이유 | 해결 방안 |
|
| 화면 유형 | 이유 | 해결 방안 |
|
||||||
|-----------|------|----------|
|
|-----------|------|----------|
|
||||||
| 간트 차트 / 타임라인 | 시간축 기반 UI 없음 | 별도 컴포넌트 개발 or 외부 라이브러리 |
|
|
||||||
| 트리 뷰 (계층 구조) | 트리 컴포넌트 미존재 | `v2-tree-view` 개발 필요 |
|
| 트리 뷰 (계층 구조) | 트리 컴포넌트 미존재 | `v2-tree-view` 개발 필요 |
|
||||||
| 그룹화 테이블 | 그룹핑 기능 미지원 | `v2-grouped-table` 개발 필요 |
|
|
||||||
| 드래그앤드롭 보드 | 칸반 스타일 UI 없음 | 별도 개발 |
|
| 드래그앤드롭 보드 | 칸반 스타일 UI 없음 | 별도 개발 |
|
||||||
| 모바일 앱 스타일 | 네이티브 앱 UI | 별도 개발 |
|
| 모바일 앱 스타일 | 네이티브 앱 UI | 별도 개발 |
|
||||||
| 복잡한 차트 | 기본 집계 외 시각화 | 차트 라이브러리 연동 |
|
| 복잡한 차트 | 기본 집계 외 시각화 | 차트 라이브러리 연동 |
|
||||||
|
|
||||||
|
> **참고**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)는 v1.1에서 추가되어 이제 지원됩니다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. V2 컴포넌트 전체 목록 (23개)
|
## 2. V2 컴포넌트 전체 목록 (25개)
|
||||||
|
|
||||||
### 2.1 입력 컴포넌트 (3개)
|
### 2.1 입력 컴포넌트 (4개)
|
||||||
|
|
||||||
| ID | 이름 | 용도 | 주요 옵션 |
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|----|------|------|----------|
|
|----|------|------|----------|
|
||||||
| `v2-input` | 입력 | 텍스트, 숫자, 비밀번호, 이메일, 전화번호, URL, 여러 줄 | inputType, required, readonly, maxLength |
|
| `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, source(distinct/static/code/entity), multiple |
|
| `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, format, showTime |
|
| `v2-date` | 날짜 | 날짜, 시간, 날짜시간 | dateType(date/time/datetime), format, range, minDate, maxDate, showToday |
|
||||||
|
| `v2-file-upload` | 파일 업로드 | 파일/이미지 업로드 | - |
|
||||||
|
|
||||||
### 2.2 표시 컴포넌트 (3개)
|
### 2.2 표시 컴포넌트 (3개)
|
||||||
|
|
||||||
| ID | 이름 | 용도 | 주요 옵션 |
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|----|------|------|----------|
|
|----|------|------|----------|
|
||||||
| `v2-text-display` | 텍스트 표시 | 라벨, 제목, 설명 텍스트 | fontSize, fontWeight, color, textAlign |
|
| `v2-text-display` | 텍스트 표시 | 라벨, 제목, 설명 텍스트 | fontSize, fontWeight, color, textAlign |
|
||||||
| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, showImage, columnMapping |
|
| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, cardSpacing, columnMapping(titleColumn/subtitleColumn/descriptionColumn/imageColumn), cardStyle(imagePosition/imageSize), dataSource(table/static/api) |
|
||||||
| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수, 최대, 최소 | items, filters, layout |
|
| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수, 최대, 최소 | items, filters, layout |
|
||||||
|
|
||||||
### 2.3 테이블/데이터 컴포넌트 (3개)
|
### 2.3 테이블/데이터 컴포넌트 (4개)
|
||||||
|
|
||||||
| ID | 이름 | 용도 | 주요 옵션 |
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|----|------|------|----------|
|
|----|------|------|----------|
|
||||||
| `v2-table-list` | 테이블 리스트 | 데이터 조회/편집 테이블 | selectedTable, columns, pagination, filter |
|
| `v2-table-list` | 테이블 리스트 | 데이터 조회/편집 테이블 | selectedTable, columns, pagination, filter, displayMode(table/card), checkbox, horizontalScroll, linkedFilters, excludeFilter, toolbar, tableStyle, autoLoad |
|
||||||
| `v2-table-search-widget` | 검색 필터 | 테이블 검색/필터/그룹 | autoSelectFirstTable, showTableSelector |
|
| `v2-table-search-widget` | 검색 필터 | 테이블 검색/필터/그룹 | autoSelectFirstTable, showTableSelector, title |
|
||||||
| `v2-pivot-grid` | 피벗 그리드 | 다차원 분석 (행/열/데이터 영역) | fields, totals, aggregation |
|
| `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 레이아웃 컴포넌트 (8개)
|
### 2.4 레이아웃 컴포넌트 (7개)
|
||||||
|
|
||||||
| ID | 이름 | 용도 | 주요 옵션 |
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|----|------|------|----------|
|
|----|------|------|----------|
|
||||||
| `v2-split-panel-layout` | 분할 패널 | 마스터-디테일 좌우 분할 | splitRatio, resizable, relation, **displayMode: custom** |
|
| `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, activeTabId |
|
| `v2-tabs-widget` | 탭 위젯 | 탭 전환, 탭 내 컴포넌트 | tabs(id/label/order/disabled/components), defaultTab, orientation(horizontal/vertical), allowCloseable, persistSelection |
|
||||||
| `v2-section-card` | 섹션 카드 | 제목+테두리 그룹화 | title, collapsible, padding |
|
| `v2-section-card` | 섹션 카드 | 제목+테두리 그룹화 | title, collapsible, padding |
|
||||||
| `v2-section-paper` | 섹션 페이퍼 | 배경색 그룹화 | backgroundColor, padding, shadow |
|
| `v2-section-paper` | 섹션 페이퍼 | 배경색 그룹화 | backgroundColor, padding, shadow |
|
||||||
| `v2-divider-line` | 구분선 | 영역 구분 | orientation, thickness |
|
| `v2-divider-line` | 구분선 | 영역 구분 | orientation, thickness |
|
||||||
| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 반복 렌더링 | dataSourceType, layout, gridColumns |
|
| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 반복 렌더링 | dataSourceType, layout, gridColumns |
|
||||||
| `v2-repeater` | 리피터 | 반복 컨트롤 | - |
|
| `v2-repeater` | 리피터 | 반복 컨트롤 (inline/modal) | - |
|
||||||
| `v2-repeat-screen-modal` | 반복 화면 모달 | 모달 반복 | - |
|
|
||||||
|
|
||||||
### 2.5 액션/특수 컴포넌트 (6개)
|
### 2.5 액션/특수 컴포넌트 (7개)
|
||||||
|
|
||||||
| ID | 이름 | 용도 | 주요 옵션 |
|
| ID | 이름 | 용도 | 주요 옵션 |
|
||||||
|----|------|------|----------|
|
|----|------|------|----------|
|
||||||
|
|
@ -82,6 +85,7 @@
|
||||||
| `v2-location-swap-selector` | 위치 교환 | 위치 선택/교환 | - |
|
| `v2-location-swap-selector` | 위치 교환 | 위치 선택/교환 | - |
|
||||||
| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 | - |
|
| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 | - |
|
||||||
| `v2-media` | 미디어 | 이미지/동영상 표시 | - |
|
| `v2-media` | 미디어 | 이미지/동영상 표시 | - |
|
||||||
|
| `v2-timeline-scheduler` | 타임라인 스케줄러 | 시간축 기반 일정/작업 관리 | - |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -261,8 +265,26 @@
|
||||||
],
|
],
|
||||||
pagination: {
|
pagination: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
pageSize: 20
|
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 // 자동 너비 조정
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -271,16 +293,44 @@
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
leftPanel: {
|
leftPanel: {
|
||||||
tableName: "마스터_테이블명"
|
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: {
|
rightPanel: {
|
||||||
|
displayMode: "table",
|
||||||
tableName: "디테일_테이블명",
|
tableName: "디테일_테이블명",
|
||||||
relation: {
|
relation: {
|
||||||
type: "detail", // join | detail | custom
|
type: "detail", // "join" | "detail" | "custom"
|
||||||
foreignKey: "master_id" // 연결 키
|
foreignKey: "master_id", // 연결 키
|
||||||
|
leftColumn: "", // 좌측 연결 컬럼
|
||||||
|
rightColumn: "", // 우측 연결 컬럼
|
||||||
|
keys: [] // 복합 키
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
splitRatio: 30 // 좌측 비율
|
splitRatio: 30, // 좌측 비율 (0-100)
|
||||||
|
resizable: true, // 리사이즈 가능
|
||||||
|
minLeftWidth: 200, // 좌측 최소 너비
|
||||||
|
minRightWidth: 300, // 우측 최소 너비
|
||||||
|
syncSelection: true, // 선택 동기화
|
||||||
|
autoLoad: true // 자동 로드
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -347,12 +397,12 @@
|
||||||
| 기능 | 상태 | 대안 |
|
| 기능 | 상태 | 대안 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 트리 뷰 (BOM, 조직도) | ❌ 미지원 | 테이블로 대체 or 별도 개발 |
|
| 트리 뷰 (BOM, 조직도) | ❌ 미지원 | 테이블로 대체 or 별도 개발 |
|
||||||
| 그룹화 테이블 | ❌ 미지원 | 일반 테이블로 대체 or 별도 개발 |
|
|
||||||
| 간트 차트 | ❌ 미지원 | 별도 개발 필요 |
|
|
||||||
| 드래그앤드롭 정렬 | ❌ 미지원 | 순서 컬럼으로 대체 |
|
| 드래그앤드롭 정렬 | ❌ 미지원 | 순서 컬럼으로 대체 |
|
||||||
| 인라인 편집 | ⚠️ 제한적 | 모달 편집으로 대체 |
|
| 인라인 편집 | ⚠️ 제한적 | 모달 편집으로 대체 |
|
||||||
| 복잡한 차트 | ❌ 미지원 | 외부 라이브러리 연동 |
|
| 복잡한 차트 | ❌ 미지원 | 외부 라이브러리 연동 |
|
||||||
|
|
||||||
|
> **v1.1 업데이트**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)가 추가되어 해당 기능은 이제 지원됩니다.
|
||||||
|
|
||||||
### 5.2 권장하지 않는 조합
|
### 5.2 권장하지 않는 조합
|
||||||
|
|
||||||
| 조합 | 이유 |
|
| 조합 | 이유 |
|
||||||
|
|
@ -555,9 +605,10 @@
|
||||||
| 탭 화면 | ✅ 완전 | v2-tabs-widget |
|
| 탭 화면 | ✅ 완전 | v2-tabs-widget |
|
||||||
| 카드 뷰 | ✅ 완전 | v2-card-display |
|
| 카드 뷰 | ✅ 완전 | v2-card-display |
|
||||||
| 피벗 분석 | ✅ 완전 | v2-pivot-grid |
|
| 피벗 분석 | ✅ 완전 | v2-pivot-grid |
|
||||||
| 그룹화 테이블 | ❌ 미지원 | 개발 필요 |
|
| 그룹화 테이블 | ✅ 지원 | v2-table-grouped |
|
||||||
|
| 타임라인/스케줄 | ✅ 지원 | v2-timeline-scheduler |
|
||||||
|
| 파일 업로드 | ✅ 지원 | v2-file-upload |
|
||||||
| 트리 뷰 | ❌ 미지원 | 개발 필요 |
|
| 트리 뷰 | ❌ 미지원 | 개발 필요 |
|
||||||
| 간트 차트 | ❌ 미지원 | 개발 필요 |
|
|
||||||
|
|
||||||
### 개발 시 핵심 원칙
|
### 개발 시 핵심 원칙
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,10 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
onRowClick,
|
onRowClick,
|
||||||
className,
|
className,
|
||||||
formData: parentFormData,
|
formData: parentFormData,
|
||||||
|
...restProps
|
||||||
}) => {
|
}) => {
|
||||||
|
// ScreenModal에서 전달된 groupedData (모달 간 데이터 전달용)
|
||||||
|
const groupedData = (restProps as any).groupedData || (restProps as any)._groupedData;
|
||||||
// 설정 병합
|
// 설정 병합
|
||||||
const config: V2RepeaterConfig = useMemo(
|
const config: V2RepeaterConfig = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
@ -681,6 +684,15 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
case "fixed":
|
case "fixed":
|
||||||
return col.autoFill.fixedValue ?? "";
|
return col.autoFill.fixedValue ?? "";
|
||||||
|
|
||||||
|
case "parentSequence": {
|
||||||
|
const parentField = col.autoFill.parentField;
|
||||||
|
const separator = col.autoFill.separator ?? "-";
|
||||||
|
const seqLength = col.autoFill.sequenceLength ?? 2;
|
||||||
|
const parentValue = parentField && mainFormData ? String(mainFormData[parentField] ?? "") : "";
|
||||||
|
const seqNum = String(rowIndex + 1).padStart(seqLength, "0");
|
||||||
|
return parentValue ? `${parentValue}${separator}${seqNum}` : seqNum;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -707,7 +719,74 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🆕 행 추가 (inline 모드 또는 모달 열기) - 비동기로 변경
|
// 모달에서 전달된 groupedData를 초기 행 데이터로 변환 (컬럼 매핑 포함)
|
||||||
|
const groupedDataProcessedRef = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!groupedData || !Array.isArray(groupedData) || groupedData.length === 0) return;
|
||||||
|
if (groupedDataProcessedRef.current) return;
|
||||||
|
|
||||||
|
groupedDataProcessedRef.current = true;
|
||||||
|
|
||||||
|
const newRows = groupedData.map((item: any, index: number) => {
|
||||||
|
const row: any = { _id: `grouped_${Date.now()}_${index}` };
|
||||||
|
|
||||||
|
for (const col of config.columns) {
|
||||||
|
const sourceValue = item[(col as any).sourceKey || col.key];
|
||||||
|
|
||||||
|
if (col.isSourceDisplay) {
|
||||||
|
row[col.key] = sourceValue ?? "";
|
||||||
|
row[`_display_${col.key}`] = sourceValue ?? "";
|
||||||
|
} else if (col.autoFill && col.autoFill.type !== "none") {
|
||||||
|
const autoValue = generateAutoFillValueSync(col, index, parentFormData);
|
||||||
|
if (autoValue !== undefined) {
|
||||||
|
row[col.key] = autoValue;
|
||||||
|
} else {
|
||||||
|
row[col.key] = "";
|
||||||
|
}
|
||||||
|
} else if (sourceValue !== undefined) {
|
||||||
|
row[col.key] = sourceValue;
|
||||||
|
} else {
|
||||||
|
row[col.key] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
setData(newRows);
|
||||||
|
onDataChange?.(newRows);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [groupedData, config.columns, generateAutoFillValueSync]);
|
||||||
|
|
||||||
|
// parentSequence 컬럼의 부모 필드 값이 변경되면 행 데이터 갱신
|
||||||
|
useEffect(() => {
|
||||||
|
if (data.length === 0) return;
|
||||||
|
|
||||||
|
const parentSeqColumns = config.columns.filter(
|
||||||
|
(col) => col.autoFill?.type === "parentSequence" && col.autoFill.parentField,
|
||||||
|
);
|
||||||
|
if (parentSeqColumns.length === 0) return;
|
||||||
|
|
||||||
|
let needsUpdate = false;
|
||||||
|
const updatedData = data.map((row, index) => {
|
||||||
|
const updatedRow = { ...row };
|
||||||
|
for (const col of parentSeqColumns) {
|
||||||
|
const newValue = generateAutoFillValueSync(col, index, parentFormData);
|
||||||
|
if (newValue !== undefined && newValue !== row[col.key]) {
|
||||||
|
updatedRow[col.key] = newValue;
|
||||||
|
needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
setData(updatedData);
|
||||||
|
onDataChange?.(updatedData);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [parentFormData, config.columns, generateAutoFillValueSync]);
|
||||||
|
|
||||||
|
// 행 추가 (inline 모드 또는 모달 열기) - 비동기로 변경
|
||||||
const handleAddRow = useCallback(async () => {
|
const handleAddRow = useCallback(async () => {
|
||||||
if (isModalMode) {
|
if (isModalMode) {
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
|
|
@ -717,7 +796,7 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
|
|
||||||
// 먼저 동기적 자동 입력 값 적용
|
// 먼저 동기적 자동 입력 값 적용
|
||||||
for (const col of config.columns) {
|
for (const col of config.columns) {
|
||||||
const autoValue = generateAutoFillValueSync(col, currentRowCount);
|
const autoValue = generateAutoFillValueSync(col, currentRowCount, parentFormData);
|
||||||
if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) {
|
if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) {
|
||||||
// 채번 규칙: 즉시 API 호출
|
// 채번 규칙: 즉시 API 호출
|
||||||
newRow[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId);
|
newRow[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId);
|
||||||
|
|
@ -731,7 +810,7 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
const newData = [...data, newRow];
|
const newData = [...data, newRow];
|
||||||
handleDataChange(newData);
|
handleDataChange(newData);
|
||||||
}
|
}
|
||||||
}, [isModalMode, config.columns, data, handleDataChange, generateAutoFillValueSync, generateNumberingCode]);
|
}, [isModalMode, config.columns, data, handleDataChange, generateAutoFillValueSync, generateNumberingCode, parentFormData]);
|
||||||
|
|
||||||
// 모달에서 항목 선택 - 비동기로 변경
|
// 모달에서 항목 선택 - 비동기로 변경
|
||||||
const handleSelectItems = useCallback(
|
const handleSelectItems = useCallback(
|
||||||
|
|
@ -760,7 +839,7 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
row[`_display_${col.key}`] = item[col.key] || "";
|
row[`_display_${col.key}`] = item[col.key] || "";
|
||||||
} else {
|
} else {
|
||||||
// 자동 입력 값 적용
|
// 자동 입력 값 적용
|
||||||
const autoValue = generateAutoFillValueSync(col, currentRowCount + index);
|
const autoValue = generateAutoFillValueSync(col, currentRowCount + index, parentFormData);
|
||||||
if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) {
|
if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) {
|
||||||
// 채번 규칙: 즉시 API 호출
|
// 채번 규칙: 즉시 API 호출
|
||||||
row[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId);
|
row[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId);
|
||||||
|
|
@ -789,6 +868,7 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||||
handleDataChange,
|
handleDataChange,
|
||||||
generateAutoFillValueSync,
|
generateAutoFillValueSync,
|
||||||
generateNumberingCode,
|
generateNumberingCode,
|
||||||
|
parentFormData,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
{ value: "numbering", label: "채번 규칙" },
|
{ value: "numbering", label: "채번 규칙" },
|
||||||
{ value: "fromMainForm", label: "메인 폼에서 복사" },
|
{ value: "fromMainForm", label: "메인 폼에서 복사" },
|
||||||
{ value: "fixed", label: "고정값" },
|
{ value: "fixed", label: "고정값" },
|
||||||
|
{ value: "parentSequence", label: "부모채번+순번 (예: WO-001-01)" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 🆕 대상 메뉴 목록 로드 (사용자 메뉴의 레벨 2)
|
// 🆕 대상 메뉴 목록 로드 (사용자 메뉴의 레벨 2)
|
||||||
|
|
@ -1393,6 +1394,56 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 부모채번+순번 설정 */}
|
||||||
|
{col.autoFill?.type === "parentSequence" && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-[10px] text-gray-600">부모 번호 필드명</Label>
|
||||||
|
<Input
|
||||||
|
value={col.autoFill?.parentField || ""}
|
||||||
|
onChange={(e) => updateColumnProp(col.key, "autoFill", {
|
||||||
|
...col.autoFill,
|
||||||
|
parentField: e.target.value,
|
||||||
|
})}
|
||||||
|
placeholder="work_order_no"
|
||||||
|
className="h-6 text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-gray-400">메인 폼에서 가져올 부모 채번 필드</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<Label className="text-[10px] text-gray-600">구분자</Label>
|
||||||
|
<Input
|
||||||
|
value={col.autoFill?.separator ?? "-"}
|
||||||
|
onChange={(e) => updateColumnProp(col.key, "autoFill", {
|
||||||
|
...col.autoFill,
|
||||||
|
separator: e.target.value,
|
||||||
|
})}
|
||||||
|
placeholder="-"
|
||||||
|
className="h-6 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<Label className="text-[10px] text-gray-600">순번 자릿수</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={5}
|
||||||
|
value={col.autoFill?.sequenceLength ?? 2}
|
||||||
|
onChange={(e) => updateColumnProp(col.key, "autoFill", {
|
||||||
|
...col.autoFill,
|
||||||
|
sequenceLength: parseInt(e.target.value) || 2,
|
||||||
|
})}
|
||||||
|
className="h-6 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-green-600">
|
||||||
|
예시: WO-20260223-005{col.autoFill?.separator ?? "-"}{String(1).padStart(col.autoFill?.sequenceLength ?? 2, "0")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -466,7 +466,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
let currentValue;
|
let currentValue;
|
||||||
if (componentType === "modal-repeater-table" ||
|
if (componentType === "modal-repeater-table" ||
|
||||||
componentType === "repeat-screen-modal" ||
|
componentType === "repeat-screen-modal" ||
|
||||||
componentType === "selected-items-detail-input") {
|
componentType === "selected-items-detail-input" ||
|
||||||
|
componentType === "v2-repeater") {
|
||||||
// EditModal/ScreenModal에서 전달된 groupedData가 있으면 우선 사용
|
// EditModal/ScreenModal에서 전달된 groupedData가 있으면 우선 사용
|
||||||
currentValue = props.groupedData || formData?.[fieldName] || [];
|
currentValue = props.groupedData || formData?.[fieldName] || [];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ export type AutoFillType =
|
||||||
| "sequence" // 순번 (1, 2, 3...)
|
| "sequence" // 순번 (1, 2, 3...)
|
||||||
| "numbering" // 채번 규칙 (관리자가 등록한 규칙 선택)
|
| "numbering" // 채번 규칙 (관리자가 등록한 규칙 선택)
|
||||||
| "fromMainForm" // 메인 폼에서 값 복사
|
| "fromMainForm" // 메인 폼에서 값 복사
|
||||||
| "fixed"; // 고정값
|
| "fixed" // 고정값
|
||||||
|
| "parentSequence"; // 부모 채번 + 순번 (예: WO-20260223-005-01)
|
||||||
|
|
||||||
// 자동 입력 설정
|
// 자동 입력 설정
|
||||||
export interface AutoFillConfig {
|
export interface AutoFillConfig {
|
||||||
|
|
@ -36,6 +37,10 @@ export interface AutoFillConfig {
|
||||||
// numbering 타입용 - 기존 채번 규칙 ID를 참조
|
// numbering 타입용 - 기존 채번 규칙 ID를 참조
|
||||||
numberingRuleId?: string; // 채번 규칙 ID (numbering_rules 테이블)
|
numberingRuleId?: string; // 채번 규칙 ID (numbering_rules 테이블)
|
||||||
selectedMenuObjid?: number; // 🆕 채번 규칙 선택을 위한 대상 메뉴 OBJID
|
selectedMenuObjid?: number; // 🆕 채번 규칙 선택을 위한 대상 메뉴 OBJID
|
||||||
|
// parentSequence 타입용
|
||||||
|
parentField?: string; // 메인 폼에서 부모 번호를 가져올 필드명
|
||||||
|
separator?: string; // 부모 번호와 순번 사이 구분자 (기본: "-")
|
||||||
|
sequenceLength?: number; // 순번 자릿수 (기본: 2 → 01, 02, 03)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 컬럼 설정
|
// 컬럼 설정
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue