docs: 컴포넌트 개발 가이드 업데이트 및 V2 + Zod 레이아웃 시스템 추가
- 컴포넌트 개발 가이드의 목차를 재구성하여 V2 + Zod 레이아웃 저장/로드 시스템에 대한 내용을 추가하였습니다. - V2 방식의 저장 및 로드 구조, 핵심 필드, 저장 및 로드 로직을 상세히 설명하였습니다. - 기존의 섹션 번호를 조정하여 새로운 내용이 자연스럽게 통합되도록 하였습니다. - Zod 스키마 구조와 관련 파일 목록을 추가하여 개발자들이 쉽게 참고할 수 있도록 하였습니다.
This commit is contained in:
parent
2fac9371c8
commit
e0ee375f01
|
|
@ -12,19 +12,20 @@ alwaysApply: false
|
||||||
## 목차
|
## 목차
|
||||||
|
|
||||||
1. [V2 컴포넌트 규칙 (최우선)](#1-v2-컴포넌트-규칙-최우선)
|
1. [V2 컴포넌트 규칙 (최우선)](#1-v2-컴포넌트-규칙-최우선)
|
||||||
2. [표준 Props 인터페이스](#2-표준-props-인터페이스)
|
2. [V2 + Zod 레이아웃 저장/로드 시스템 (핵심)](#2-v2--zod-레이아웃-저장로드-시스템-핵심)
|
||||||
3. [멀티테넌시 (company_code)](#3-멀티테넌시-company_code)
|
3. [표준 Props 인터페이스](#3-표준-props-인터페이스)
|
||||||
4. [디자인 모드 vs 인터랙티브 모드](#4-디자인-모드-vs-인터랙티브-모드)
|
4. [멀티테넌시 (company_code)](#4-멀티테넌시-company_code)
|
||||||
5. [로딩 및 에러 처리](#5-로딩-및-에러-처리)
|
5. [디자인 모드 vs 인터랙티브 모드](#5-디자인-모드-vs-인터랙티브-모드)
|
||||||
6. [테이블 컬럼 기반 입력 위젯](#6-테이블-컬럼-기반-입력-위젯)
|
6. [로딩 및 에러 처리](#6-로딩-및-에러-처리)
|
||||||
7. [컴포넌트별 테이블 설정](#7-컴포넌트별-테이블-설정)
|
7. [테이블 컬럼 기반 입력 위젯](#7-테이블-컬럼-기반-입력-위젯)
|
||||||
8. [엔티티 조인 컬럼 활용](#8-엔티티-조인-컬럼-활용)
|
8. [컴포넌트별 테이블 설정](#8-컴포넌트별-테이블-설정)
|
||||||
9. [폼 데이터 관리](#9-폼-데이터-관리)
|
9. [엔티티 조인 컬럼 활용](#9-엔티티-조인-컬럼-활용)
|
||||||
10. [다국어 지원](#10-다국어-지원)
|
10. [폼 데이터 관리](#10-폼-데이터-관리)
|
||||||
11. [저장 버튼 및 플로우 연동](#11-저장-버튼-및-플로우-연동)
|
11. [다국어 지원](#11-다국어-지원)
|
||||||
12. [표준 코드 스타일 가이드](#12-표준-코드-스타일-가이드)
|
12. [저장 버튼 및 플로우 연동](#12-저장-버튼-및-플로우-연동)
|
||||||
13. [성능 최적화](#13-성능-최적화)
|
13. [표준 코드 스타일 가이드](#13-표준-코드-스타일-가이드)
|
||||||
14. [체크리스트](#14-체크리스트)
|
14. [성능 최적화](#14-성능-최적화)
|
||||||
|
15. [체크리스트](#15-체크리스트)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -90,7 +91,212 @@ export const V2TableListDefinition = createComponentDefinition({
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 표준 Props 인터페이스
|
## 2. V2 + Zod 레이아웃 저장/로드 시스템 (핵심)
|
||||||
|
|
||||||
|
### 핵심 원칙
|
||||||
|
|
||||||
|
**컴포넌트 코드 수정 시 모든 화면에 자동 반영되도록 V2 + Zod 기반 저장/로드 방식을 사용합니다.**
|
||||||
|
|
||||||
|
```
|
||||||
|
저장: component_url + overrides (차이값만)
|
||||||
|
로드: Zod 기본값 + overrides 병합
|
||||||
|
```
|
||||||
|
|
||||||
|
### 기존 방식 vs V2 방식
|
||||||
|
|
||||||
|
| 항목 | 기존 (문제점) | V2 (해결) |
|
||||||
|
|-----|-------------|----------|
|
||||||
|
| 저장 | 전체 설정 "박제" | url + overrides (차이값만) |
|
||||||
|
| 코드 수정 반영 | 안 됨 | 자동 반영 |
|
||||||
|
| 테이블 | `screen_layouts` (다중 레코드) | `screen_layouts_v2` (1 레코드) |
|
||||||
|
|
||||||
|
### 저장 구조 (screen_layouts_v2)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "2.0",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "comp_xxx",
|
||||||
|
"url": "@/lib/registry/components/unified-select",
|
||||||
|
"position": { "x": 100, "y": 50 },
|
||||||
|
"size": { "width": 180, "height": 30 },
|
||||||
|
"displayOrder": 0,
|
||||||
|
"overrides": {
|
||||||
|
"tableName": "warehouse_info",
|
||||||
|
"columnName": "warehouse_code",
|
||||||
|
"label": "창고코드",
|
||||||
|
"webType": "select"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 핵심 필드: overrides에 반드시 포함되어야 하는 속성
|
||||||
|
|
||||||
|
컴포넌트가 테이블 컬럼에서 드래그되어 생성된 경우, 다음 속성들이 `overrides`에 저장됩니다:
|
||||||
|
|
||||||
|
| 속성 | 설명 | 예시 |
|
||||||
|
|-----|------|-----|
|
||||||
|
| `tableName` | 연결된 테이블명 | `"warehouse_info"` |
|
||||||
|
| `columnName` | 연결된 컬럼명 | `"warehouse_code"` |
|
||||||
|
| `label` | 표시 라벨 | `"창고코드"` |
|
||||||
|
| `required` | 필수 입력 여부 | `true` / `false` |
|
||||||
|
| `readonly` | 읽기 전용 여부 | `true` / `false` |
|
||||||
|
| `inputType` | 입력 타입 | `"text"`, `"select"`, `"date"` 등 |
|
||||||
|
| `webType` | 웹 타입 | `"text"`, `"select"`, `"date"` 등 |
|
||||||
|
| `codeCategory` | 코드 카테고리 (코드 타입인 경우) | `"WAREHOUSE_TYPE"` |
|
||||||
|
|
||||||
|
### 저장 로직 (convertLegacyToV2)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/utils/layoutV2Converter.ts
|
||||||
|
|
||||||
|
export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
|
||||||
|
const components = legacyLayout.components.map((comp, index) => {
|
||||||
|
const componentType = comp.componentType || comp.widgetType || comp.type;
|
||||||
|
const url = getComponentUrl(componentType);
|
||||||
|
const defaults = getDefaultsByUrl(url);
|
||||||
|
|
||||||
|
// 상위 레벨 속성들도 overrides에 포함 (중요!)
|
||||||
|
const topLevelProps: Record<string, any> = {};
|
||||||
|
if (comp.tableName) topLevelProps.tableName = comp.tableName;
|
||||||
|
if (comp.columnName) topLevelProps.columnName = comp.columnName;
|
||||||
|
if (comp.label) topLevelProps.label = comp.label;
|
||||||
|
if (comp.required !== undefined) topLevelProps.required = comp.required;
|
||||||
|
if (comp.readonly !== undefined) topLevelProps.readonly = comp.readonly;
|
||||||
|
|
||||||
|
// componentConfig에서 차이값만 추출
|
||||||
|
const configOverrides = extractCustomConfig(comp.componentConfig || {}, defaults);
|
||||||
|
|
||||||
|
// 병합
|
||||||
|
const overrides = { ...topLevelProps, ...configOverrides };
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: comp.id,
|
||||||
|
url: url,
|
||||||
|
position: comp.position,
|
||||||
|
size: comp.size,
|
||||||
|
displayOrder: index,
|
||||||
|
overrides: overrides,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { version: "2.0", components };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 로드 로직 (convertV2ToLegacy)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/utils/layoutV2Converter.ts
|
||||||
|
|
||||||
|
export function convertV2ToLegacy(v2Layout: LayoutV2): LegacyLayoutData {
|
||||||
|
const components = v2Layout.components.map((comp) => {
|
||||||
|
const componentType = getComponentTypeFromUrl(comp.url);
|
||||||
|
const defaults = getDefaultsByUrl(comp.url);
|
||||||
|
const mergedConfig = mergeComponentConfig(defaults, comp.overrides);
|
||||||
|
|
||||||
|
// overrides에서 상위 레벨 속성들 복원
|
||||||
|
const overrides = comp.overrides || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: comp.id,
|
||||||
|
componentType: componentType,
|
||||||
|
position: comp.position,
|
||||||
|
size: comp.size,
|
||||||
|
componentConfig: mergedConfig,
|
||||||
|
// 상위 레벨 속성 복원 (중요!)
|
||||||
|
tableName: overrides.tableName,
|
||||||
|
columnName: overrides.columnName,
|
||||||
|
label: overrides.label || "",
|
||||||
|
required: overrides.required,
|
||||||
|
readonly: overrides.readonly,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { components };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zod 스키마 구조
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// frontend/lib/schemas/componentConfig.ts
|
||||||
|
|
||||||
|
// 컴포넌트별 overrides 스키마
|
||||||
|
export const unifiedSelectOverridesSchema = z.object({
|
||||||
|
mode: z.enum(["dropdown", "combobox", "radio", "checkbox"]).default("dropdown"),
|
||||||
|
source: z.enum(["static", "code", "entity", "db", "distinct"]).default("distinct"),
|
||||||
|
multiple: z.boolean().default(false),
|
||||||
|
searchable: z.boolean().default(true),
|
||||||
|
placeholder: z.string().default("선택하세요"),
|
||||||
|
}).passthrough(); // 정의되지 않은 필드도 통과 (tableName, columnName 등)
|
||||||
|
|
||||||
|
// 스키마 레지스트리
|
||||||
|
export const componentOverridesSchemaRegistry: Record<string, z.ZodType<any>> = {
|
||||||
|
"unified-select": unifiedSelectOverridesSchema,
|
||||||
|
"unified-input": unifiedInputOverridesSchema,
|
||||||
|
"v2-table-list": v2TableListOverridesSchema,
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// 기본값 레지스트리
|
||||||
|
export const componentDefaultsRegistry: Record<string, any> = {
|
||||||
|
"unified-select": {
|
||||||
|
mode: "dropdown",
|
||||||
|
source: "distinct", // 기본: 테이블 컬럼에서 자동 로드
|
||||||
|
multiple: false,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### unified-select 자동 옵션 로드
|
||||||
|
|
||||||
|
`webType`이 `"select"`인 컬럼을 드래그하면:
|
||||||
|
|
||||||
|
1. **저장 시**: `tableName`, `columnName`이 `overrides`에 저장됨
|
||||||
|
2. **로드 시**: `source`가 `"distinct"`이면 자동으로 `/entity/{tableName}/distinct/{columnName}` API 호출
|
||||||
|
3. **결과**: 해당 컬럼의 고유 값들이 옵션으로 표시됨
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// DynamicComponentRenderer.tsx
|
||||||
|
|
||||||
|
case "unified-select":
|
||||||
|
return (
|
||||||
|
<UnifiedSelect
|
||||||
|
{...commonProps}
|
||||||
|
config={{
|
||||||
|
mode: config.mode || "dropdown",
|
||||||
|
source: config.source || "distinct", // 기본: 테이블에서 자동 로드
|
||||||
|
// ...
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 관련 파일
|
||||||
|
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|------|------|
|
||||||
|
| `frontend/lib/schemas/componentConfig.ts` | Zod 스키마 및 기본값 레지스트리 |
|
||||||
|
| `frontend/lib/utils/layoutV2Converter.ts` | V2 ↔ Legacy 변환 유틸리티 |
|
||||||
|
| `frontend/lib/api/screen.ts` | `getLayoutV2`, `saveLayoutV2` API |
|
||||||
|
| `backend-node/src/services/screenManagementService.ts` | 백엔드 저장/로드 로직 |
|
||||||
|
|
||||||
|
### 새 컴포넌트 추가 시 체크리스트
|
||||||
|
|
||||||
|
1. [ ] `componentConfig.ts`에 Zod 스키마 추가 (`.passthrough()` 필수)
|
||||||
|
2. [ ] `componentOverridesSchemaRegistry`에 등록
|
||||||
|
3. [ ] `componentDefaultsRegistry`에 기본값 등록
|
||||||
|
4. [ ] 테이블 컬럼 드래그 시 `tableName`, `columnName` 저장 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 표준 Props 인터페이스
|
||||||
|
|
||||||
### 컴포넌트가 받아야 하는 표준 Props
|
### 컴포넌트가 받아야 하는 표준 Props
|
||||||
|
|
||||||
|
|
@ -150,7 +356,7 @@ export const MyComponent: React.FC<StandardComponentProps> = ({
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 멀티테넌시 (company_code)
|
## 4. 멀티테넌시 (company_code)
|
||||||
|
|
||||||
### 핵심 원칙
|
### 핵심 원칙
|
||||||
|
|
||||||
|
|
@ -199,7 +405,7 @@ const response = await apiClient.post(`/table-management/tables/${tableName}/add
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 디자인 모드 vs 인터랙티브 모드
|
## 5. 디자인 모드 vs 인터랙티브 모드
|
||||||
|
|
||||||
### 모드 구분
|
### 모드 구분
|
||||||
|
|
||||||
|
|
@ -257,7 +463,7 @@ const handleClick = useCallback(() => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 로딩 및 에러 처리
|
## 6. 로딩 및 에러 처리
|
||||||
|
|
||||||
### 로딩 상태 관리
|
### 로딩 상태 관리
|
||||||
|
|
||||||
|
|
@ -378,7 +584,7 @@ if (!silentActions.includes(actionType)) {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 테이블 컬럼 기반 입력 위젯
|
## 7. 테이블 컬럼 기반 입력 위젯
|
||||||
|
|
||||||
### 드래그 방식으로 입력 폼 생성
|
### 드래그 방식으로 입력 폼 생성
|
||||||
|
|
||||||
|
|
@ -492,7 +698,7 @@ const handleSave = async (context: ButtonActionContext) => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 컴포넌트별 테이블 설정
|
## 8. 컴포넌트별 테이블 설정
|
||||||
|
|
||||||
### 핵심 원칙
|
### 핵심 원칙
|
||||||
|
|
||||||
|
|
@ -606,7 +812,7 @@ const response = await apiClient.get(
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 엔티티 조인 컬럼 활용
|
## 9. 엔티티 조인 컬럼 활용
|
||||||
|
|
||||||
### 핵심 원칙
|
### 핵심 원칙
|
||||||
|
|
||||||
|
|
@ -712,7 +918,7 @@ const getEntityJoinValue = (item: any, columnName: string): any => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 폼 데이터 관리
|
## 10. 폼 데이터 관리
|
||||||
|
|
||||||
### 통합 폼 시스템 (UnifiedFormContext)
|
### 통합 폼 시스템 (UnifiedFormContext)
|
||||||
|
|
||||||
|
|
@ -756,7 +962,7 @@ const handleChange = useCallback((value: any) => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 다국어 지원
|
## 11. 다국어 지원
|
||||||
|
|
||||||
### 타입 정의 시 다국어 필드 추가
|
### 타입 정의 시 다국어 필드 추가
|
||||||
|
|
||||||
|
|
@ -824,7 +1030,7 @@ const MyComponent = ({ component }) => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. 저장 버튼 및 플로우 연동
|
## 12. 저장 버튼 및 플로우 연동
|
||||||
|
|
||||||
### beforeFormSave 이벤트 처리 (필수)
|
### beforeFormSave 이벤트 처리 (필수)
|
||||||
|
|
||||||
|
|
@ -912,7 +1118,7 @@ const MyFormComponent = ({ formData, onFormDataChange }) => {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. 표준 코드 스타일 가이드
|
## 13. 표준 코드 스타일 가이드
|
||||||
|
|
||||||
**`v2-unified-repeater`** 컴포넌트를 표준으로 삼아 동일한 구조로 작성합니다.
|
**`v2-unified-repeater`** 컴포넌트를 표준으로 삼아 동일한 구조로 작성합니다.
|
||||||
|
|
||||||
|
|
@ -1276,7 +1482,7 @@ const tableName = config?.dataSource?.tableName;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. 성능 최적화
|
## 14. 성능 최적화
|
||||||
|
|
||||||
### useMemo로 계산 비용 줄이기
|
### useMemo로 계산 비용 줄이기
|
||||||
|
|
||||||
|
|
@ -1452,7 +1658,7 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. 체크리스트
|
## 15. 체크리스트
|
||||||
|
|
||||||
### V2 컴포넌트 규칙
|
### V2 컴포넌트 규칙
|
||||||
|
|
||||||
|
|
@ -1461,6 +1667,16 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
|
||||||
- [ ] Definition 이름에 `V2` 접두사 사용
|
- [ ] Definition 이름에 `V2` 접두사 사용
|
||||||
- [ ] 원본 폴더 수정하지 않음
|
- [ ] 원본 폴더 수정하지 않음
|
||||||
|
|
||||||
|
### V2 + Zod 레이아웃 시스템
|
||||||
|
|
||||||
|
- [ ] `componentConfig.ts`에 Zod 스키마 추가 (`.passthrough()` 필수)
|
||||||
|
- [ ] `componentOverridesSchemaRegistry`에 컴포넌트 등록
|
||||||
|
- [ ] `componentDefaultsRegistry`에 기본값 등록
|
||||||
|
- [ ] 테이블 컬럼 드래그 시 `tableName`, `columnName` 저장 확인
|
||||||
|
- [ ] `convertLegacyToV2`에서 상위 레벨 속성 포함 확인
|
||||||
|
- [ ] `convertV2ToLegacy`에서 상위 레벨 속성 복원 확인
|
||||||
|
- [ ] unified-select는 `source: "distinct"` 기본값 확인
|
||||||
|
|
||||||
### 표준 Props
|
### 표준 Props
|
||||||
|
|
||||||
- [ ] `component`, `isDesignMode` props 지원
|
- [ ] `component`, `isDesignMode` props 지원
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue