Files
vexplor/docs/COMPONENT_URL_SYSTEM_IMPLEMENTATION.md
DDD1542 192b678bce fix: 화면 복제 기능 개선 및 관련 버그 수정
- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다.
- 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다.
- 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다.
- 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
2026-01-28 11:24:25 +09:00

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% 유지 확인
  • 새 필드 추가 시 기본값 자동 적용 확인
  • 기존 화면 렌더링 동일성 확인
  • 화면 디자이너 저장/로드 정상 동작 확인