- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다. - 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다. - 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다. - 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
234 lines
6.0 KiB
Markdown
234 lines
6.0 KiB
Markdown
ㅡㄹ ㅣ # 컴포넌트 URL 시스템 구현 완료
|
|
|
|
## 실행 일시: 2026-01-27
|
|
|
|
## 1. 목표
|
|
|
|
- 컴포넌트 코드 수정 시 **모든 회사에 즉시 반영** ✅
|
|
- 회사별 고유 설정은 **JSON으로 안전하게 관리** (Zod 검증) ✅
|
|
- 기존 화면 **100% 동일하게 렌더링** 보장 ✅
|
|
|
|
---
|
|
|
|
## 2. 완료된 작업
|
|
|
|
### 2.1 DB 테이블 생성
|
|
- `screen_layouts_v3` 테이블 생성 완료
|
|
- 4,414개 레코드 마이그레이션 완료
|
|
|
|
### 2.2 파일 생성/수정
|
|
| 파일 | 상태 |
|
|
|-----|-----|
|
|
| `frontend/lib/schemas/componentConfig.ts` | ✅ 신규 생성 |
|
|
| `backend-node/src/services/screenManagementService.ts` | ✅ getLayoutV3 추가 |
|
|
| `backend-node/src/controllers/screenManagementController.ts` | ✅ getLayoutV3 추가 |
|
|
| `backend-node/src/routes/screenManagementRoutes.ts` | ✅ 라우트 추가 |
|
|
|
|
### 2.3 API 엔드포인트
|
|
```
|
|
GET /api/screen-management/screens/:screenId/layout-v3
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 핵심 구조
|
|
|
|
### 2.1 컴포넌트 코드 (파일 시스템)
|
|
|
|
```
|
|
frontend/lib/registry/components/{component-name}/
|
|
├── index.ts # 렌더링 로직, UI
|
|
├── schema.ts # Zod 스키마 + 기본값
|
|
└── types.ts # 타입 정의
|
|
```
|
|
|
|
### 2.2 DB 구조
|
|
|
|
```sql
|
|
screen_layouts_v3 (
|
|
layout_id SERIAL PRIMARY KEY,
|
|
screen_id INTEGER REFERENCES screen_definitions(screen_id),
|
|
component_id VARCHAR(100) UNIQUE NOT NULL,
|
|
|
|
-- 컴포넌트 URL (파일 경로)
|
|
component_url VARCHAR(200) NOT NULL,
|
|
-- 예: "@/lib/registry/components/split-panel-layout"
|
|
|
|
-- 회사별 커스텀 설정 (비즈니스 데이터만)
|
|
custom_config JSONB NOT NULL DEFAULT '{}',
|
|
|
|
-- 레이아웃 정보
|
|
parent_id VARCHAR(100),
|
|
position_x INTEGER NOT NULL DEFAULT 0,
|
|
position_y INTEGER NOT NULL DEFAULT 0,
|
|
width INTEGER NOT NULL DEFAULT 100,
|
|
height INTEGER NOT NULL DEFAULT 100,
|
|
display_order INTEGER DEFAULT 0,
|
|
|
|
-- 기타
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 대상 컴포넌트 (고수준)
|
|
|
|
| 컴포넌트 | 개수 | 우선순위 |
|
|
|---------|-----|---------|
|
|
| split-panel-layout | 129 | 높음 |
|
|
| tabs-widget | 74 | 높음 |
|
|
| modal-repeater-table | 68 | 높음 |
|
|
| category-manager | 69 | 중간 |
|
|
| flow-widget | 11 | 중간 |
|
|
| table-list | 280 | 높음 |
|
|
| table-search-widget | 353 | 높음 |
|
|
| conditional-container | 53 | 중간 |
|
|
| selected-items-detail-input | 83 | 중간 |
|
|
|
|
---
|
|
|
|
## 4. 작업 단계
|
|
|
|
### Phase 1: 스키마 정의
|
|
- [ ] split-panel-layout/schema.ts
|
|
- [ ] tabs-widget/schema.ts
|
|
- [ ] modal-repeater-table/schema.ts
|
|
- [ ] table-list/schema.ts
|
|
- [ ] table-search-widget/schema.ts
|
|
- [ ] 기타 컴포넌트들
|
|
|
|
### Phase 2: DB 테이블 생성
|
|
- [ ] screen_layouts_v3 테이블 생성
|
|
- [ ] 인덱스 생성
|
|
|
|
### Phase 3: 마이그레이션
|
|
- [ ] 기존 데이터에서 component_url 추출
|
|
- [ ] 기존 데이터에서 custom_config 분리
|
|
- [ ] 검증 (기존 화면과 동일 렌더링)
|
|
|
|
### Phase 4: 백엔드 수정
|
|
- [ ] getLayoutV3 API 추가
|
|
- [ ] saveLayoutV3 API 추가
|
|
|
|
### Phase 5: 프론트엔드 수정
|
|
- [ ] 렌더링 로직에 스키마 병합 적용
|
|
- [ ] 화면 디자이너 저장 로직 수정
|
|
|
|
---
|
|
|
|
## 5. Zod 스키마 설계 원칙
|
|
|
|
### 5.1 기본값 (코드에서 관리)
|
|
```typescript
|
|
// 컴포넌트 UI/동작 관련 - 코드 수정 시 전체 반영
|
|
const baseDefaults = {
|
|
resizable: true,
|
|
splitRatio: 30,
|
|
syncSelection: true,
|
|
};
|
|
```
|
|
|
|
### 5.2 커스텀 설정 (DB에서 관리)
|
|
```typescript
|
|
// 비즈니스 데이터 - 회사별 개별 관리
|
|
const customConfigSchema = z.object({
|
|
leftPanel: z.object({
|
|
title: z.string().optional(),
|
|
tableName: z.string(),
|
|
columns: z.array(z.any()).default([]),
|
|
}).passthrough(),
|
|
rightPanel: z.object({
|
|
title: z.string().optional(),
|
|
tableName: z.string(),
|
|
relation: z.any().optional(),
|
|
}).passthrough(),
|
|
}).passthrough();
|
|
```
|
|
|
|
### 5.3 병합 로직
|
|
```typescript
|
|
function mergeConfig(baseDefaults: any, customConfig: any) {
|
|
// 1. 스키마로 customConfig 파싱 (없는 필드는 기본값)
|
|
const parsed = customConfigSchema.parse(customConfig);
|
|
|
|
// 2. 기본값과 병합
|
|
return { ...baseDefaults, ...parsed };
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 렌더링 흐름
|
|
|
|
```
|
|
1. DB 조회
|
|
├─ component_url: "@/lib/registry/components/split-panel-layout"
|
|
└─ custom_config: { leftPanel: { tableName: "sales_order_mng", ... } }
|
|
|
|
2. 컴포넌트 로드
|
|
└─ ComponentRegistry.get("split-panel-layout")
|
|
|
|
3. 스키마 로드
|
|
└─ import { schema, baseDefaults } from "./schema"
|
|
|
|
4. 설정 병합
|
|
└─ baseDefaults + schema.parse(custom_config)
|
|
|
|
5. 렌더링
|
|
└─ <SplitPanelLayout config={mergedConfig} />
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 마이그레이션 전략
|
|
|
|
### 7.1 component_url 추출
|
|
```sql
|
|
-- properties.componentType → component_url 변환
|
|
UPDATE screen_layouts_v3
|
|
SET component_url = '@/lib/registry/components/' || (properties->>'componentType')
|
|
WHERE properties->>'componentType' IS NOT NULL;
|
|
```
|
|
|
|
### 7.2 custom_config 분리
|
|
```javascript
|
|
// 기존 componentConfig에서 비즈니스 데이터만 추출
|
|
function extractCustomConfig(componentType, componentConfig) {
|
|
const baseKeys = getBaseKeys(componentType); // 코드 기본값 키들
|
|
const customConfig = {};
|
|
|
|
for (const key of Object.keys(componentConfig)) {
|
|
if (!baseKeys.includes(key)) {
|
|
customConfig[key] = componentConfig[key];
|
|
}
|
|
}
|
|
|
|
return customConfig;
|
|
}
|
|
```
|
|
|
|
### 7.3 검증
|
|
```javascript
|
|
// 기존 렌더링과 동일한지 확인
|
|
function verify(original, migrated) {
|
|
const originalRender = renderWithConfig(original.componentConfig);
|
|
const migratedRender = renderWithConfig(
|
|
merge(baseDefaults, migrated.custom_config)
|
|
);
|
|
|
|
return deepEqual(originalRender, migratedRender);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 체크리스트
|
|
|
|
- [ ] 컴포넌트 코드 수정 → 전체 회사 즉시 반영 확인
|
|
- [ ] 기존 고유 설정 100% 유지 확인
|
|
- [ ] 새 필드 추가 시 기본값 자동 적용 확인
|
|
- [ ] 기존 화면 렌더링 동일성 확인
|
|
- [ ] 화면 디자이너 저장/로드 정상 동작 확인
|