feat: V2Repeater 컴포넌트 추가 및 DynamicComponentRenderer 통합 처리 개선
- V2Repeater 컴포넌트를 추가하여 인라인 테이블, 모달, 버튼 등 다양한 반복 데이터 관리를 지원합니다. - V2RepeaterConfigPanel을 통해 반복 컴포넌트의 설정 패널을 통합하였습니다. - DynamicComponentRenderer에서 모든 v2- 컴포넌트를 ComponentRegistry에서 통합 처리하도록 개선하여 코드의 일관성을 높였습니다. - 레거시 타입을 v2 컴포넌트로 매핑하는 로직을 정리하여 가독성을 향상시켰습니다.
This commit is contained in:
parent
95bef976a5
commit
3ab8c9b5a0
|
|
@ -20,6 +20,7 @@ import { V2Group } from "./V2Group";
|
|||
import { V2Media } from "./V2Media";
|
||||
import { V2Biz } from "./V2Biz";
|
||||
import { V2Hierarchy } from "./V2Hierarchy";
|
||||
import { V2Repeater } from "./V2Repeater";
|
||||
|
||||
// 설정 패널 import
|
||||
import { V2InputConfigPanel } from "./config-panels/V2InputConfigPanel";
|
||||
|
|
@ -31,6 +32,7 @@ import { V2GroupConfigPanel } from "./config-panels/V2GroupConfigPanel";
|
|||
import { V2MediaConfigPanel } from "./config-panels/V2MediaConfigPanel";
|
||||
import { V2BizConfigPanel } from "./config-panels/V2BizConfigPanel";
|
||||
import { V2HierarchyConfigPanel } from "./config-panels/V2HierarchyConfigPanel";
|
||||
import { V2RepeaterConfigPanel } from "./config-panels/V2RepeaterConfigPanel";
|
||||
|
||||
// V2 컴포넌트 정의
|
||||
const v2ComponentDefinitions: ComponentDefinition[] = [
|
||||
|
|
@ -179,6 +181,31 @@ const v2ComponentDefinitions: ComponentDefinition[] = [
|
|||
dataSource: "static",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "v2-repeater",
|
||||
name: "통합 반복",
|
||||
description: "인라인 테이블, 모달, 버튼 등 다양한 반복 데이터 관리를 지원하는 통합 컴포넌트",
|
||||
category: ComponentCategory.V2,
|
||||
webType: "entity" as WebType,
|
||||
component: V2Repeater as any,
|
||||
tags: ["repeater", "table", "modal", "button", "data", "v2"],
|
||||
defaultSize: { width: 600, height: 300 },
|
||||
configPanel: V2RepeaterConfigPanel as any,
|
||||
defaultConfig: {
|
||||
renderMode: "inline",
|
||||
dataSource: {
|
||||
tableName: "",
|
||||
foreignKey: "",
|
||||
referenceKey: "",
|
||||
},
|
||||
columns: [],
|
||||
features: {
|
||||
showAddButton: true,
|
||||
showDeleteButton: true,
|
||||
inlineEdit: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,25 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
import React from "react";
|
||||
import { ComponentData } from "@/types/screen";
|
||||
import { DynamicLayoutRenderer } from "./DynamicLayoutRenderer";
|
||||
import { ComponentRegistry } from "./ComponentRegistry";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
|
||||
// V2 컴포넌트 import
|
||||
import {
|
||||
V2Input,
|
||||
V2Select,
|
||||
V2Date,
|
||||
V2List,
|
||||
V2Layout,
|
||||
V2Group,
|
||||
V2Media,
|
||||
V2Biz,
|
||||
V2Hierarchy,
|
||||
} from "@/components/v2";
|
||||
import { V2Repeater } from "@/components/v2/V2Repeater";
|
||||
|
||||
// 통합 폼 시스템 import
|
||||
import { useV2FormOptional } from "@/components/v2/V2FormContext";
|
||||
|
||||
|
|
@ -189,11 +175,13 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
// 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용
|
||||
const rawComponentType = (component as any).componentType || component.type;
|
||||
|
||||
// 🆕 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지)
|
||||
// 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지)
|
||||
const mapToV2ComponentType = (type: string | undefined): string | undefined => {
|
||||
if (!type) return type;
|
||||
// 이미 v2- 또는 v2- 접두사가 있으면 그대로 반환
|
||||
if (type.startsWith("v2-") || type.startsWith("v2-")) return type;
|
||||
|
||||
// 이미 v2- 접두사가 있으면 그대로 반환
|
||||
if (type.startsWith("v2-")) return type;
|
||||
|
||||
// 레거시 타입을 v2로 매핑 시도
|
||||
const v2Type = `v2-${type}`;
|
||||
// v2 버전이 등록되어 있는지 확인
|
||||
|
|
@ -208,306 +196,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
|
||||
// 컴포넌트 타입 변환 완료
|
||||
|
||||
// 🆕 V2 폼 시스템 연동 (최상위에서 한 번만 호출)
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const v2FormContextLocal = useV2FormOptional();
|
||||
|
||||
// 🆕 V2 컴포넌트 처리
|
||||
if (componentType?.startsWith("v2-")) {
|
||||
const v2Type = componentType as string;
|
||||
const config = (component as any).componentConfig || {};
|
||||
const fieldName = (component as any).columnName || component.id;
|
||||
|
||||
// V2 시스템이 있으면 거기서 값 가져오기, 없으면 props.formData 사용
|
||||
const currentValue = v2FormContextLocal
|
||||
? v2FormContextLocal.getValue(fieldName)
|
||||
: props.formData?.[fieldName];
|
||||
|
||||
// 🆕 통합 onChange 핸들러 - 양쪽 시스템에 전파
|
||||
const handleChange = (value: any) => {
|
||||
// 1. V2 시스템에 전파
|
||||
if (v2FormContextLocal) {
|
||||
v2FormContextLocal.setValue(fieldName, value);
|
||||
}
|
||||
// 2. 레거시 콜백도 호출 (호환성)
|
||||
if (props.onFormDataChange) {
|
||||
props.onFormDataChange(fieldName, value);
|
||||
}
|
||||
};
|
||||
|
||||
// 공통 props
|
||||
const commonProps = {
|
||||
id: component.id,
|
||||
label: (component as any).label,
|
||||
required: (component as any).required,
|
||||
readonly: (component as any).readonly,
|
||||
// conditionalDisabled가 true이면 비활성화
|
||||
disabled: (component as any).disabled || props.disabledFields?.includes(fieldName) || props.conditionalDisabled,
|
||||
value: currentValue,
|
||||
onChange: handleChange,
|
||||
tableName: (component as any).tableName || props.tableName,
|
||||
columnName: fieldName,
|
||||
style: component.style,
|
||||
size: component.size,
|
||||
position: component.position,
|
||||
};
|
||||
|
||||
switch (v2Type) {
|
||||
// V2 입력 컴포넌트
|
||||
case "v2-input":
|
||||
return (
|
||||
<V2Input
|
||||
v2Type="V2Input"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.inputType || config.type || "text",
|
||||
inputType: config.inputType || config.type || "text",
|
||||
format: config.format,
|
||||
placeholder: config.placeholder,
|
||||
mask: config.mask,
|
||||
min: config.min,
|
||||
max: config.max,
|
||||
step: config.step,
|
||||
buttonText: config.buttonText,
|
||||
buttonVariant: config.buttonVariant,
|
||||
autoGeneration: config.autoGeneration,
|
||||
tableName: (component as any).tableName || props.tableName,
|
||||
}}
|
||||
autoGeneration={config.autoGeneration}
|
||||
formData={props.formData}
|
||||
originalData={props.originalData}
|
||||
/>
|
||||
);
|
||||
|
||||
// V2 선택 컴포넌트
|
||||
case "v2-select":
|
||||
// v2-select는 항상 테이블 컬럼에서 distinct 값을 자동 로드
|
||||
return (
|
||||
<V2Select
|
||||
v2Type="V2Select"
|
||||
{...commonProps}
|
||||
config={{
|
||||
mode: config.mode || "dropdown",
|
||||
source: config.source || "distinct", // 기본값: 테이블 컬럼에서 distinct 조회
|
||||
multiple: config.multiple,
|
||||
searchable: config.searchable,
|
||||
codeGroup: config.codeGroup,
|
||||
codeCategory: config.codeCategory,
|
||||
table: config.table,
|
||||
valueColumn: config.valueColumn,
|
||||
labelColumn: config.labelColumn,
|
||||
// 엔티티(참조 테이블) 관련 속성
|
||||
entityTable: config.entityTable,
|
||||
entityValueColumn: config.entityValueColumn,
|
||||
entityLabelColumn: config.entityLabelColumn,
|
||||
entityValueField: config.entityValueField,
|
||||
entityLabelField: config.entityLabelField,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// V2 날짜 컴포넌트
|
||||
case "v2-date":
|
||||
return (
|
||||
<V2Date
|
||||
v2Type="V2Date"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.dateType || config.type || "date",
|
||||
format: config.format,
|
||||
range: config.range,
|
||||
minDate: config.minDate,
|
||||
maxDate: config.maxDate,
|
||||
showToday: config.showToday,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case "v2-list":
|
||||
// 데이터 소스: config.data > props.tableDisplayData > []
|
||||
const listData = config.data?.length > 0 ? config.data : props.tableDisplayData || [];
|
||||
|
||||
return (
|
||||
<V2List
|
||||
v2Type="V2List"
|
||||
{...commonProps}
|
||||
config={{
|
||||
viewMode: config.viewMode || "table",
|
||||
columns: config.columns || [],
|
||||
source: config.source || "static",
|
||||
sortable: config.sortable,
|
||||
pagination: config.pagination,
|
||||
searchable: config.searchable,
|
||||
editable: config.editable,
|
||||
pageable: config.pageable,
|
||||
pageSize: config.pageSize,
|
||||
cardConfig: config.cardConfig,
|
||||
dataSource: {
|
||||
table: config.dataSource?.table || props.tableName,
|
||||
},
|
||||
}}
|
||||
data={listData}
|
||||
selectedRows={props.selectedRowsData || []}
|
||||
onRowSelect={(rows) => {
|
||||
// 항상 선택된 데이터를 전달 (modalDataStore에 자동 저장됨)
|
||||
if (props.onSelectedRowsChange) {
|
||||
props.onSelectedRowsChange(
|
||||
rows.map((r: any) => r.id || r.objid),
|
||||
rows,
|
||||
props.sortBy,
|
||||
props.sortOrder,
|
||||
undefined,
|
||||
props.tableDisplayData,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case "v2-layout":
|
||||
return (
|
||||
<V2Layout
|
||||
v2Type="V2Layout"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.layoutType || config.type || "grid",
|
||||
columns: config.columns,
|
||||
gap: config.gap,
|
||||
direction: config.direction,
|
||||
use12Column: config.use12Column,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</V2Layout>
|
||||
);
|
||||
|
||||
case "v2-group":
|
||||
return (
|
||||
<V2Group
|
||||
v2Type="V2Group"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.groupType || config.type || "section",
|
||||
collapsible: config.collapsible,
|
||||
defaultOpen: config.defaultOpen,
|
||||
tabs: config.tabs || [],
|
||||
showHeader: config.showHeader,
|
||||
}}
|
||||
title={config.title}
|
||||
>
|
||||
{children}
|
||||
</V2Group>
|
||||
);
|
||||
|
||||
case "v2-media":
|
||||
return (
|
||||
<V2Media
|
||||
v2Type="V2Media"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.mediaType || config.type || "image",
|
||||
accept: config.accept,
|
||||
maxSize: config.maxSize,
|
||||
multiple: config.multiple,
|
||||
preview: config.preview,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case "v2-biz":
|
||||
return (
|
||||
<V2Biz
|
||||
v2Type="V2Biz"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.bizType || config.type || "flow",
|
||||
flowConfig: config.flowConfig,
|
||||
rackConfig: config.rackConfig,
|
||||
numberingConfig: config.numberingConfig,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case "v2-hierarchy":
|
||||
return (
|
||||
<V2Hierarchy
|
||||
v2Type="V2Hierarchy"
|
||||
{...commonProps}
|
||||
config={{
|
||||
type: config.hierarchyType || config.type || "tree",
|
||||
viewMode: config.viewMode || "tree",
|
||||
dataSource: config.dataSource || "static",
|
||||
maxLevel: config.maxLevel,
|
||||
draggable: config.draggable,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
case "v2-repeater":
|
||||
// 🆕 저장 설정 추출 (useCustomTable, mainTableName, foreignKeyColumn)
|
||||
const repeaterTargetTable = config.useCustomTable && config.mainTableName
|
||||
? config.mainTableName
|
||||
: config.dataSource?.tableName;
|
||||
|
||||
return (
|
||||
<V2Repeater
|
||||
config={{
|
||||
renderMode: config.renderMode || "inline",
|
||||
// 🆕 저장 설정 추가
|
||||
useCustomTable: config.useCustomTable,
|
||||
mainTableName: config.mainTableName,
|
||||
foreignKeyColumn: config.foreignKeyColumn,
|
||||
foreignKeySourceColumn: config.foreignKeySourceColumn, // 🆕 FK 소스 컬럼 추가
|
||||
dataSource: {
|
||||
tableName: config.dataSource?.tableName || props.tableName || "",
|
||||
foreignKey: config.dataSource?.foreignKey || "",
|
||||
referenceKey: config.dataSource?.referenceKey || "",
|
||||
sourceTable: config.dataSource?.sourceTable,
|
||||
displayColumn: config.dataSource?.displayColumn,
|
||||
},
|
||||
columns: config.columns || [],
|
||||
modal: config.modal,
|
||||
button: config.button,
|
||||
features: config.features || {
|
||||
showAddButton: true,
|
||||
showDeleteButton: true,
|
||||
inlineEdit: false,
|
||||
dragSort: false,
|
||||
showRowNumber: false,
|
||||
selectable: false,
|
||||
multiSelect: false,
|
||||
},
|
||||
}}
|
||||
parentId={props.formData?.[config.dataSource?.referenceKey] || props.formData?.id}
|
||||
onDataChange={(data) => {
|
||||
// 🆕 formData 업데이트 (부모로 데이터 전달)
|
||||
if (props.onFormDataChange) {
|
||||
// _targetTable 메타데이터 추가
|
||||
const dataWithTargetTable = data.map((item: any) => ({
|
||||
...item,
|
||||
_targetTable: repeaterTargetTable,
|
||||
}));
|
||||
props.onFormDataChange(component.id || "repeaterData", dataWithTargetTable);
|
||||
}
|
||||
}}
|
||||
onRowClick={(row) => {
|
||||
}}
|
||||
onButtonClick={(action, row, buttonConfig) => {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center rounded border-2 border-dashed border-amber-300 bg-amber-50 p-4">
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-sm font-medium text-amber-600">V2 컴포넌트</div>
|
||||
<div className="text-xs text-amber-500">알 수 없는 타입: {v2Type}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
// 🆕 모든 v2- 컴포넌트는 ComponentRegistry에서 통합 처리
|
||||
// (v2-input, v2-select, v2-repeat-container 등 모두 동일하게 처리)
|
||||
|
||||
// 🎯 카테고리 타입 우선 처리 (inputType 또는 webType 확인)
|
||||
const inputType = (component as any).componentConfig?.inputType || (component as any).inputType;
|
||||
|
|
|
|||
Loading…
Reference in New Issue