- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다. - 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다. - 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다. - 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
6.0 KiB
6.0 KiB
ㅡㄹ ㅣ # 컴포넌트 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 구조
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 기본값 (코드에서 관리)
// 컴포넌트 UI/동작 관련 - 코드 수정 시 전체 반영
const baseDefaults = {
resizable: true,
splitRatio: 30,
syncSelection: true,
};
5.2 커스텀 설정 (DB에서 관리)
// 비즈니스 데이터 - 회사별 개별 관리
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 병합 로직
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 추출
-- 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 분리
// 기존 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 검증
// 기존 렌더링과 동일한지 확인
function verify(original, migrated) {
const originalRender = renderWithConfig(original.componentConfig);
const migratedRender = renderWithConfig(
merge(baseDefaults, migrated.custom_config)
);
return deepEqual(originalRender, migratedRender);
}
8. 체크리스트
- 컴포넌트 코드 수정 → 전체 회사 즉시 반영 확인
- 기존 고유 설정 100% 유지 확인
- 새 필드 추가 시 기본값 자동 적용 확인
- 기존 화면 렌더링 동일성 확인
- 화면 디자이너 저장/로드 정상 동작 확인