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 { V2Media } from "./V2Media";
|
||||||
import { V2Biz } from "./V2Biz";
|
import { V2Biz } from "./V2Biz";
|
||||||
import { V2Hierarchy } from "./V2Hierarchy";
|
import { V2Hierarchy } from "./V2Hierarchy";
|
||||||
|
import { V2Repeater } from "./V2Repeater";
|
||||||
|
|
||||||
// 설정 패널 import
|
// 설정 패널 import
|
||||||
import { V2InputConfigPanel } from "./config-panels/V2InputConfigPanel";
|
import { V2InputConfigPanel } from "./config-panels/V2InputConfigPanel";
|
||||||
|
|
@ -31,6 +32,7 @@ import { V2GroupConfigPanel } from "./config-panels/V2GroupConfigPanel";
|
||||||
import { V2MediaConfigPanel } from "./config-panels/V2MediaConfigPanel";
|
import { V2MediaConfigPanel } from "./config-panels/V2MediaConfigPanel";
|
||||||
import { V2BizConfigPanel } from "./config-panels/V2BizConfigPanel";
|
import { V2BizConfigPanel } from "./config-panels/V2BizConfigPanel";
|
||||||
import { V2HierarchyConfigPanel } from "./config-panels/V2HierarchyConfigPanel";
|
import { V2HierarchyConfigPanel } from "./config-panels/V2HierarchyConfigPanel";
|
||||||
|
import { V2RepeaterConfigPanel } from "./config-panels/V2RepeaterConfigPanel";
|
||||||
|
|
||||||
// V2 컴포넌트 정의
|
// V2 컴포넌트 정의
|
||||||
const v2ComponentDefinitions: ComponentDefinition[] = [
|
const v2ComponentDefinitions: ComponentDefinition[] = [
|
||||||
|
|
@ -179,6 +181,31 @@ const v2ComponentDefinitions: ComponentDefinition[] = [
|
||||||
dataSource: "static",
|
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";
|
"use client";
|
||||||
|
|
||||||
import React, { useCallback } from "react";
|
import React from "react";
|
||||||
import { ComponentData } from "@/types/screen";
|
import { ComponentData } from "@/types/screen";
|
||||||
import { DynamicLayoutRenderer } from "./DynamicLayoutRenderer";
|
import { DynamicLayoutRenderer } from "./DynamicLayoutRenderer";
|
||||||
import { ComponentRegistry } from "./ComponentRegistry";
|
import { ComponentRegistry } from "./ComponentRegistry";
|
||||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
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
|
||||||
import { useV2FormOptional } from "@/components/v2/V2FormContext";
|
import { useV2FormOptional } from "@/components/v2/V2FormContext";
|
||||||
|
|
||||||
|
|
@ -189,11 +175,13 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
// 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용
|
// 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용
|
||||||
const rawComponentType = (component as any).componentType || component.type;
|
const rawComponentType = (component as any).componentType || component.type;
|
||||||
|
|
||||||
// 🆕 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지)
|
// 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지)
|
||||||
const mapToV2ComponentType = (type: string | undefined): string | undefined => {
|
const mapToV2ComponentType = (type: string | undefined): string | undefined => {
|
||||||
if (!type) return type;
|
if (!type) return type;
|
||||||
// 이미 v2- 또는 v2- 접두사가 있으면 그대로 반환
|
|
||||||
if (type.startsWith("v2-") || type.startsWith("v2-")) return type;
|
// 이미 v2- 접두사가 있으면 그대로 반환
|
||||||
|
if (type.startsWith("v2-")) return type;
|
||||||
|
|
||||||
// 레거시 타입을 v2로 매핑 시도
|
// 레거시 타입을 v2로 매핑 시도
|
||||||
const v2Type = `v2-${type}`;
|
const v2Type = `v2-${type}`;
|
||||||
// v2 버전이 등록되어 있는지 확인
|
// v2 버전이 등록되어 있는지 확인
|
||||||
|
|
@ -208,306 +196,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
|
|
||||||
// 컴포넌트 타입 변환 완료
|
// 컴포넌트 타입 변환 완료
|
||||||
|
|
||||||
// 🆕 V2 폼 시스템 연동 (최상위에서 한 번만 호출)
|
// 🆕 모든 v2- 컴포넌트는 ComponentRegistry에서 통합 처리
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// (v2-input, v2-select, v2-repeat-container 등 모두 동일하게 처리)
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🎯 카테고리 타입 우선 처리 (inputType 또는 webType 확인)
|
// 🎯 카테고리 타입 우선 처리 (inputType 또는 webType 확인)
|
||||||
const inputType = (component as any).componentConfig?.inputType || (component as any).inputType;
|
const inputType = (component as any).componentConfig?.inputType || (component as any).inputType;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue