10 KiB
10 KiB
V3 메타 컴포넌트 - 자체 데이터 파이프라인 구축 계획
핵심 원칙: V3 메타 컴포넌트는 V2를 래핑하지 않는다.
자체적으로 완전히 동작하는 렌더링 + 데이터 파이프라인을 가진다.
V2의 좋은 부분(API, EventBus, formData 패턴)은 재사용하되, 렌더러는 새로 만든다.
아키텍처
[V3 메타 컴포넌트]
│
├─ FieldRenderer ─── formData/onFormDataChange + entityJoinApi + validation
├─ DataViewRenderer ─── entityJoinApi.getTableDataWithJoins + 페이징/정렬/필터
├─ ActionRenderer ─── ButtonActionExecutor + v2EventBus
├─ SearchRenderer ─── v2EventBus로 DataView 필터링
├─ LayoutRenderer ─── children 관리 (순수 컨테이너)
├─ DisplayRenderer ─── formData 바인딩 + 포맷팅
└─ ModalRenderer ─── 모달 상태 + 내부 폼
재사용하는 V2 인프라 (수정 없이 그대로 사용)
| 모듈 | 파일 | 용도 |
|---|---|---|
| entityJoinApi | frontend/lib/api/entityJoin.ts |
테이블 데이터 조회 (조인 포함) |
| DynamicFormApi | frontend/lib/api/dynamicForm.ts |
폼 저장/검증 |
| screenApi | frontend/lib/api/screen.ts |
CRUD API (add/edit/delete) |
| v2EventBus | frontend/lib/v2-core/events/EventBus.ts |
컴포넌트 간 이벤트 통신 |
| V2_EVENTS | frontend/lib/v2-core/events/types.ts |
이벤트 타입 상수 |
| ButtonActionExecutor | frontend/lib/utils/buttonActions.ts |
버튼 액션 실행 |
| apiClient | frontend/lib/api/client.ts |
HTTP 클라이언트 |
| formData 패턴 | props 전달 | formData, onFormDataChange(fieldName, value) |
새로 만드는 V3 전용 모듈
Phase 1: FieldRenderer (자체 데이터 바인딩)
목표: 하나의 FieldRenderer가 webType에 따라 모든 입력 필드를 렌더링
파일: frontend/lib/meta-components/Field/FieldRenderer.tsx
기능 요구사항:
formData[columnName]에서 현재 값 읽기onFormDataChange(columnName, newValue)호출로 값 변경- webType별 렌더링:
text,number,email,tel,url,password: shadcn<Input>사용textarea: shadcn<Textarea>사용date,datetime: shadcn date picker 또는 기존<input type="date">사용select: shadcn<Select>사용, options는 config.options에서 가져오거나 API 호출checkbox,radio,toggle: shadcn<Checkbox>,<RadioGroup>,<Switch>entity: 엔티티 검색 입력 (autocomplete) -entityJoinApi활용file: 파일 업로드 - 기존 file API 활용numbering: 읽기 전용 채번 필드
- 라벨 표시 (config.label, labelPosition)
- 필수/읽기전용/비활성 상태 처리
- 검증 규칙 적용 (config.validation)
Props 인터페이스:
interface FieldRendererProps {
id: string;
config: FieldComponentConfig;
// 데이터 바인딩 (상위에서 전달)
formData?: Record<string, any>;
onFormDataChange?: (fieldName: string, value: any) => void;
// 컨텍스트
tableName?: string;
companyCode?: string;
screenId?: number;
// UI 모드
isDesignMode?: boolean;
disabled?: boolean;
className?: string;
}
shadcn 컴포넌트 사용:
- Input, Textarea, Select, Checkbox, RadioGroup, Switch, Label, DatePicker
- 모두 기존 설치된 shadcn/ui 활용
엔티티 검색 구현:
- config.join이 있으면 엔티티 검색 모드
- entityJoinApi를 사용하여 검색 대상 테이블 조회
- Popover + Command (shadcn combobox 패턴) 사용
Phase 2: DataViewRenderer (자체 데이터 Fetching)
목표: 하나의 DataViewRenderer가 테이블/카드/트리 뷰를 모두 처리
파일: frontend/lib/meta-components/DataView/DataViewRenderer.tsx
기능 요구사항:
- 컴포넌트 마운트 시
entityJoinApi.getTableDataWithJoins(config.tableName, ...)호출 - 페이징 처리 (config.pageSize, 현재 페이지 상태 관리)
- 정렬 (컬럼 헤더 클릭)
- 행 선택 (단일/다중)
- 행 클릭 시 formData에 선택된 행 데이터 반영
- v2EventBus.subscribe(TABLE_REFRESH) 구독 → 데이터 재로드
- v2EventBus.emitSync(TABLE_DATA_CHANGE) 발행 → 선택 변경 시
- viewMode에 따라 테이블/카드 전환
Props 인터페이스:
interface DataViewRendererProps {
id: string;
config: DataViewComponentConfig;
// 선택된 행
selectedRowsData?: any[];
onSelectedRowsChange?: (rows: any[], data: any[]) => void;
// 컨텍스트
tableName?: string;
companyCode?: string;
screenId?: number;
// formData (마스터-디테일 연동용)
formData?: Record<string, any>;
onFormDataChange?: (fieldName: string, value: any) => void;
// UI
isDesignMode?: boolean;
className?: string;
onRefresh?: () => void;
}
테이블 렌더링:
- shadcn
<Table>기반 - 컬럼은 config.columns 또는 API에서 컬럼 메타데이터 자동 조회
- 페이징: 하단에 페이지네이션 컴포넌트
- 정렬: 컬럼 헤더 클릭으로 ASC/DESC 전환
디자인 모드:
- 데이터 로드 안 함
- 컬럼 헤더만 표시 + 샘플 빈 행 표시
- "DataView: {tableName}" 배지 표시
Phase 3: ActionRenderer (자체 CRUD 실행)
목표: 버튼 클릭 시 config.steps에 정의된 액션을 순차 실행
파일: frontend/lib/meta-components/Action/ActionRenderer.tsx
기능 요구사항:
- shadcn
<Button>렌더링 (variant, icon, label) - 클릭 시 config.steps를 순차 실행:
save:screenApi.addTableData()또는screenApi.editTableData()호출delete:screenApi.deleteTableData()호출refresh:v2EventBus.emitSync(TABLE_REFRESH)발행navigate: router.push로 화면 이동openModal: 모달 열기 이벤트api: 커스텀 API 호출toast: 토스트 메시지 표시validate: 폼 검증
- config.confirmDialog가 있으면 실행 전 확인 대화상자
- config.enableCondition에 따라 버튼 활성/비활성
Props 인터페이스:
interface ActionRendererProps {
id: string;
config: ActionComponentConfig;
// 데이터
formData?: Record<string, any>;
selectedRowsData?: any[];
// 컨텍스트
tableName?: string;
companyCode?: string;
screenId?: number;
userId?: string;
// 콜백
onRefresh?: () => void;
// UI
isDesignMode?: boolean;
disabled?: boolean;
className?: string;
}
Phase 4: SearchRenderer, LayoutRenderer, DisplayRenderer, ModalRenderer
SearchRenderer
- 검색 필드들 렌더링 (config.fields 기반)
- "검색" 버튼 클릭 시 필터 조건 구성
- v2EventBus로 연결된 DataView에 필터 전달
- 또는 onSearch 콜백으로 필터 전달
LayoutRenderer
- config.mode에 따라 레이아웃 구성:
columns: CSS Grid 기반 수평 분할rows: Flex 기반 수직 분할tabs: shadcn<Tabs>사용card: shadcn<Card>사용accordion: shadcn<Accordion>사용
- children을 각 영역에 배치
DisplayRenderer
- config.displayType에 따라 표시:
text/heading: 텍스트/제목 표시divider: 구분선badge: 뱃지alert: 알림stat: 통계 카드
- config.dataBinding이 있으면 formData에서 값 바인딩
ModalRenderer
- 트리거 버튼 렌더링
- 클릭 시 shadcn
<Dialog>열기 - 모달 내부에 폼 필드 자동 구성 (config.content.formConfig 기반)
- 저장/취소 버튼
RealtimePreviewDynamic.tsx 수정
메타 컴포넌트 렌더링 시 metaToV2 변환 없이 직접 V3 렌더러를 호출하도록 수정:
// 메타 컴포넌트는 자체 렌더러로 직접 렌더링
if (isMetaComponent(component)) {
return (
<MetaComponentRenderer
component={{ id, type: componentType, config: componentConfig }}
formData={formData}
onFormDataChange={onFormDataChange}
selectedRowsData={selectedRowsData}
onSelectedRowsChange={onSelectedRowsChange}
tableName={tableName}
companyCode={companyCode}
screenId={screenId}
userId={userId}
onRefresh={onRefresh}
isDesignMode={isDesignMode}
/>
);
}
// V2 컴포넌트는 기존 DynamicComponentRenderer로
return <DynamicComponentRenderer ... />;
MetaComponentRenderer.tsx 수정
각 개별 렌더러에 모든 필요한 props를 전달하는 진정한 디스패처로 수정:
export function MetaComponentRenderer({ component, ...runtimeProps }) {
const { type, config } = component;
switch (type) {
case "meta-field":
return <FieldRenderer id={component.id} config={config} {...runtimeProps} />;
case "meta-dataview":
return <DataViewRenderer id={component.id} config={config} {...runtimeProps} />;
case "meta-action":
return <ActionRenderer id={component.id} config={config} {...runtimeProps} />;
// ...
}
}
테스트 기준
Phase 1 (FieldRenderer) 완료 조건:
- text/number/email 입력 → formData에 값 반영
- select → 옵션 목록 표시, 선택 시 formData 반영
- date → 날짜 선택 가능
- 라벨, 필수 표시, 읽기전용 동작
- 디자인 모드에서 비활성 상태로 표시
Phase 2 (DataViewRenderer) 완료 조건:
- 테이블명으로 데이터 자동 로드
- 테이블 형태로 데이터 표시
- 페이징 동작
- 행 클릭 시 선택
- TABLE_REFRESH 이벤트에 반응
Phase 3 (ActionRenderer) 완료 조건:
- 버튼 렌더링 (variant, icon)
- 클릭 시 save → API 호출 → 토스트
- 클릭 시 delete → 확인 → API 호출
- 클릭 시 refresh → 테이블 새로고침
전체 통합 테스트:
- V3 변환 후 입력 필드에 데이터 입력 가능
- V3 변환 후 테이블에 데이터 로드됨
- V3 변환 후 버튼 클릭 시 저장/삭제 동작
- TypeScript 빌드 에러 0개
실행 순서
- frontend 에이전트: Phase 1 (FieldRenderer) 구현 + 빌드 테스트
- frontend 에이전트: Phase 2 (DataViewRenderer) 구현 + 빌드 테스트
- frontend 에이전트: Phase 3 (ActionRenderer) 구현 + 빌드 테스트
- frontend 에이전트: Phase 4 (나머지) 구현 + 빌드 테스트
- frontend 에이전트: RealtimePreviewDynamic + MetaComponentRenderer 수정
- browser-use: 전체 통합 브라우저 테스트