- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다. - 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다. - 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다. - 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
12 KiB
12 KiB
컴포넌트 레이아웃 V2 아키텍처
최종 업데이트: 2026-01-27
1. 개요
1.1 목표
- 핵심 목표: 컴포넌트 코드 수정 시 모든 화면에 자동 반영
- 문제 해결: 기존 JSON "박제" 방식으로 인한 코드 수정 미반영 문제
- 방식: 1 레코드 방식 (화면당 1개 레코드, JSON에 모든 컴포넌트 포함)
1.2 핵심 원칙
저장: component_url + overrides (차이값만)
로드: 코드 기본값 + overrides 병합 (Zod)
이전 방식 (문제점):
// 전체 설정 박제 → 코드 수정해도 반영 안 됨
{
"componentType": "table-list",
"componentConfig": {
"columns": [...],
"pagination": true,
"pageSize": 20,
// ... 수백 줄의 설정
}
}
V2 방식 (해결):
// url로 코드 참조 + 차이값만 저장
{
"url": "@/lib/registry/components/table-list",
"overrides": {
"tableName": "user_info",
"columns": ["id", "name"]
}
}
2. 데이터베이스 구조
2.1 테이블 정의
CREATE TABLE screen_layouts_v2 (
layout_id SERIAL PRIMARY KEY,
screen_id INTEGER NOT NULL,
company_code VARCHAR(20) NOT NULL,
layout_data JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(screen_id, company_code)
);
-- 인덱스
CREATE INDEX idx_v2_screen_id ON screen_layouts_v2(screen_id);
CREATE INDEX idx_v2_company_code ON screen_layouts_v2(company_code);
CREATE INDEX idx_v2_screen_company ON screen_layouts_v2(screen_id, company_code);
2.2 layout_data 구조
{
"version": "2.0",
"components": [
{
"id": "comp_xxx",
"url": "@/lib/registry/components/table-list",
"position": { "x": 0, "y": 0 },
"size": { "width": 100, "height": 50 },
"displayOrder": 0,
"overrides": {
"tableName": "user_info",
"columns": ["id", "name", "email"]
}
},
{
"id": "comp_yyy",
"url": "@/lib/registry/components/button-primary",
"position": { "x": 0, "y": 60 },
"size": { "width": 20, "height": 5 },
"displayOrder": 1,
"overrides": {
"label": "저장",
"variant": "default"
}
}
],
"updatedAt": "2026-01-27T12:00:00Z"
}
2.3 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
id |
string | 컴포넌트 고유 ID |
url |
string | 컴포넌트 코드 경로 (필수) |
position |
object | 캔버스 내 위치 {x, y} |
size |
object | 크기 {width, height} |
displayOrder |
number | 렌더링 순서 |
overrides |
object | 기본값과 다른 설정만 (차이값) |
3. API 정의
3.1 레이아웃 조회
GET /api/screen-management/screens/:screenId/layout-v2
응답:
{
"success": true,
"data": {
"version": "2.0",
"components": [...]
}
}
로직:
- 회사별 레이아웃 먼저 조회
- 없으면 공통(*) 레이아웃 조회
- 없으면 null 반환
3.2 레이아웃 저장
POST /api/screen-management/screens/:screenId/layout-v2
요청:
{
"components": [
{
"id": "comp_xxx",
"url": "@/lib/registry/components/table-list",
"position": { "x": 0, "y": 0 },
"size": { "width": 100, "height": 50 },
"overrides": { ... }
}
]
}
로직:
- 권한 확인
- 버전 정보 추가
- UPSERT (있으면 업데이트, 없으면 삽입)
4. 컴포넌트 URL 규칙
4.1 URL 형식
@/lib/registry/components/{component-name}
4.2 현재 등록된 컴포넌트
| URL | 설명 |
|---|---|
@/lib/registry/components/table-list |
테이블 리스트 |
@/lib/registry/components/button-primary |
기본 버튼 |
@/lib/registry/components/text-input |
텍스트 입력 |
@/lib/registry/components/select-basic |
기본 셀렉트 |
@/lib/registry/components/date-input |
날짜 입력 |
@/lib/registry/components/split-panel-layout |
분할 패널 |
@/lib/registry/components/tabs-widget |
탭 위젯 |
@/lib/registry/components/card-display |
카드 디스플레이 |
@/lib/registry/components/flow-widget |
플로우 위젯 |
@/lib/registry/components/category-management |
카테고리 관리 |
@/lib/registry/components/pivot-table |
피벗 테이블 |
@/lib/registry/components/unified-grid |
통합 그리드 |
5. Zod 스키마 관리
5.1 목적
- 런타임 타입 검증
- 기본값 자동 적용
- overrides 유효성 검사
5.2 구조
// frontend/lib/schemas/componentConfig.ts
import { z } from "zod";
// 공통 스키마
export const baseComponentSchema = z.object({
id: z.string(),
url: z.string(),
position: z.object({
x: z.number().default(0),
y: z.number().default(0),
}),
size: z.object({
width: z.number().default(100),
height: z.number().default(100),
}),
displayOrder: z.number().default(0),
overrides: z.record(z.any()).default({}),
});
// 컴포넌트별 overrides 스키마
export const tableListOverridesSchema = z.object({
tableName: z.string().optional(),
columns: z.array(z.string()).optional(),
pagination: z.boolean().default(true),
pageSize: z.number().default(20),
});
export const buttonOverridesSchema = z.object({
label: z.string().default("버튼"),
variant: z.enum(["default", "destructive", "outline", "ghost"]).default("default"),
icon: z.string().optional(),
});
5.3 사용 방법
// 로드 시: 코드 기본값 + overrides 병합
function loadComponent(component: any) {
const schema = getSchemaByUrl(component.url);
const defaults = schema.parse({});
const merged = deepMerge(defaults, component.overrides);
return merged;
}
// 저장 시: 기본값과 다른 부분만 추출
function saveComponent(component: any, config: any) {
const schema = getSchemaByUrl(component.url);
const defaults = schema.parse({});
const overrides = extractDiff(defaults, config);
return { ...component, overrides };
}
6. 마이그레이션 현황
6.1 완료된 작업
| 작업 | 상태 | 날짜 |
|---|---|---|
| screen_layouts_v2 테이블 생성 | ✅ 완료 | 2026-01-27 |
| 기존 데이터 마이그레이션 | ✅ 완료 | 2026-01-27 |
| 백엔드 API 추가 (getLayoutV2, saveLayoutV2) | ✅ 완료 | 2026-01-27 |
| 프론트엔드 API 클라이언트 추가 | ✅ 완료 | 2026-01-27 |
| Zod 스키마 V2 확장 | ✅ 완료 | 2026-01-27 |
| V2 변환 유틸리티 (layoutV2Converter.ts) | ✅ 완료 | 2026-01-27 |
| ScreenDesigner V2 API 연동 | ✅ 완료 | 2026-01-27 |
6.2 마이그레이션 통계
마이그레이션 대상 화면: 1,347개
성공: 1,347개 (100%)
실패: 0개
컴포넌트 많은 화면 TOP 5:
- screen 74: 25개 컴포넌트
- screen 1204: 18개 컴포넌트
- screen 1242: 18개 컴포넌트
- screen 119: 18개 컴포넌트
- screen 1255: 18개 컴포넌트
7. 남은 작업
7.1 필수 작업
| 작업 | 우선순위 | 예상 공수 | 상태 |
|---|---|---|---|
| 프론트엔드 디자이너 V2 API 연동 | 높음 | 3일 | ✅ 완료 |
| Zod 스키마 컴포넌트별 정의 | 높음 | 2일 | ✅ 완료 |
| V2 변환 유틸리티 | 높음 | 1일 | ✅ 완료 |
| 테스트 및 검증 | 중간 | 2일 | 🔄 진행 필요 |
7.2 선택 작업
| 작업 | 우선순위 | 예상 공수 |
|---|---|---|
| 기존 API (layout, layout-v1) 제거 | 낮음 | 1일 |
| 기존 테이블 (screen_layouts, screen_layouts_v1) 정리 | 낮음 | 1일 |
| 마이그레이션 검증 도구 | 낮음 | 1일 |
| 컴포넌트별 기본값 레지스트리 확장 | 낮음 | 2일 |
8. 개발 가이드
8.1 새 컴포넌트 추가 시
-
컴포넌트 코드 생성
frontend/lib/registry/components/{component-name}/ ├── index.ts ├── {ComponentName}Renderer.tsx └── types.ts -
Zod 스키마 정의
// frontend/lib/schemas/components/{component-name}.ts export const {componentName}OverridesSchema = z.object({ // 컴포넌트 고유 설정 }); -
레지스트리 등록
// frontend/lib/registry/components/index.ts export { default as {ComponentName} } from "./{component-name}";
8.2 화면 저장 시
// 디자이너에서 저장 시
async function handleSave() {
const layoutData = {
components: components.map(comp => ({
id: comp.id,
url: comp.url,
position: comp.position,
size: comp.size,
displayOrder: comp.displayOrder,
overrides: extractOverrides(comp.url, comp.config) // 차이값만 추출
}))
};
await screenApi.saveLayoutV2(screenId, layoutData);
}
8.3 화면 로드 시
// 화면 렌더러에서 로드 시
async function loadScreen(screenId: number) {
const layoutData = await screenApi.getLayoutV2(screenId);
const components = layoutData.components.map(comp => {
const defaults = getDefaultsByUrl(comp.url); // Zod 기본값
const mergedConfig = deepMerge(defaults, comp.overrides);
return {
...comp,
config: mergedConfig
};
});
return components;
}
9. 비교: 기존 vs V2
| 항목 | 기존 (다중 레코드) | V2 (1 레코드) |
|---|---|---|
| 레코드 수 | 화면당 N개 (컴포넌트 수) | 화면당 1개 |
| 저장 방식 | 전체 설정 박제 | url + overrides |
| 코드 수정 반영 | ❌ 안 됨 | ✅ 자동 반영 |
| 중복 데이터 | 있음 (DB 컬럼 + JSON) | 없음 |
| 공사량 | - | 테이블 변경 필요 |
10. 관련 파일
10.1 백엔드
backend-node/src/services/screenManagementService.ts- getLayoutV2, saveLayoutV2backend-node/src/controllers/screenManagementController.ts- API 엔드포인트backend-node/src/routes/screenManagementRoutes.ts- 라우트 정의
10.2 프론트엔드
frontend/lib/api/screen.ts- getLayoutV2, saveLayoutV2 클라이언트frontend/lib/schemas/componentConfig.ts- Zod 스키마 및 V2 유틸리티frontend/lib/utils/layoutV2Converter.ts- V2 ↔ Legacy 변환 유틸리티frontend/components/screen/ScreenDesigner.tsx- V2 API 연동 (USE_V2_API 플래그)frontend/lib/registry/components/- 컴포넌트 레지스트리
10.3 데이터베이스
screen_layouts_v2- V2 레이아웃 테이블
11. FAQ
Q1: 기존 화면은 어떻게 되나요?
기존 화면은 마이그레이션되어 screen_layouts_v2에 저장됩니다. 디자이너가 V2 API를 사용하도록 수정되면 자동으로 새 구조를 사용합니다.
Q2: 컴포넌트 코드를 수정하면 정말 전체 반영되나요?
네. overrides에는 차이값만 저장되고, 로드 시 코드의 기본값과 병합됩니다. 기본값을 수정하면 모든 화면에 반영됩니다.
Q3: 회사별 설정은 어떻게 관리하나요?
company_code 컬럼으로 회사별 레이아웃을 분리합니다. 회사별 레이아웃이 없으면 공통(*) 레이아웃을 사용합니다.
Q4: 기존 테이블(screen_layouts)은 언제 삭제하나요?
V2가 안정화되고 모든 기능이 정상 동작하는지 확인된 후에 삭제합니다. 최소 1개월 이상 병행 운영 권장.
12. 변경 이력
| 날짜 | 변경 내용 | 작성자 |
|---|---|---|
| 2026-01-27 | 초안 작성, 테이블 생성, 마이그레이션, API 추가 | Claude |
| 2026-01-27 | Zod 스키마 V2 확장, 변환 유틸리티, ScreenDesigner 연동 | Claude |