From f7dda7a666adacce3b9e7817c769bfa8ca49490a Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 4 Feb 2026 10:21:57 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20V2Input=20=EB=B0=8F=20DynamicComponentR?= =?UTF-8?q?enderer=EC=97=90=EC=84=9C=20tableName=20=EB=B0=8F=20inputType?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - V2Input 컴포넌트에서 tableName을 여러 소스(프롭스, 설정, 오버라이드, 화면 정보)에서 추출하도록 로직을 개선하였습니다. - inputType을 props에서 직접 전달받거나 config에서 설정된 값으로 확인하도록 수정하여 유연성을 높였습니다. - DynamicComponentRenderer에서 v2-input 컴포넌트의 tableName을 올바르게 처리하도록 업데이트하였습니다. - 채번 규칙 ID를 formData에 저장하는 로직을 개선하여 데이터 처리의 일관성을 강화하였습니다. --- frontend/components/v2/V2Input.tsx | 31 ++++++++++++++----- .../lib/registry/DynamicComponentRenderer.tsx | 23 ++++++++++++-- frontend/lib/utils/buttonActions.ts | 13 +++++--- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index edaefffb..abc062cb 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -361,8 +361,17 @@ export const V2Input = forwardRef((props, ref) => const [isGeneratingNumbering, setIsGeneratingNumbering] = useState(false); const hasGeneratedNumberingRef = useRef(false); - // tableName 추출 (props에서 전달받거나 config에서) - const tableName = (props as any).tableName || (config as any).tableName; + // tableName 추출 (여러 소스에서 확인) + // 1. props에서 직접 전달받은 값 + // 2. config에서 설정된 값 + // 3. 컴포넌트 overrides에서 설정된 값 (V2 레이아웃) + // 4. screenInfo에서 화면 테이블명 + const tableName = + (props as any).tableName || + (config as any).tableName || + (props as any).component?.tableName || + (props as any).component?.overrides?.tableName || + (props as any).screenInfo?.tableName; // 수정 모드 여부 확인 const originalData = (props as any).originalData || (props as any)._originalData; @@ -445,8 +454,10 @@ export const V2Input = forwardRef((props, ref) => // formData에서 카테고리 관련 값 추출 (채번 파트에서 카테고리 사용 시) // 채번 필드 자체의 값은 제외해야 함 (무한 루프 방지) + // inputType을 여러 소스에서 확인 + const propsInputType = (props as any).inputType; const categoryValuesForNumbering = useMemo(() => { - const inputType = config.inputType || config.type || "text"; + const inputType = propsInputType || config.inputType || config.type || "text"; if (inputType !== "numbering") return ""; // formData에서 category 타입 필드 값들을 추출 (채번 필드 자체는 제외) const categoryFields: Record = {}; @@ -458,12 +469,13 @@ export const V2Input = forwardRef((props, ref) => } } return JSON.stringify(categoryFields); - }, [config.inputType, config.type, formData, columnName]); + }, [propsInputType, config.inputType, config.type, formData, columnName]); // 채번 타입 자동생성 로직 (테이블 관리에서 설정된 numberingRuleId 사용) useEffect(() => { const generateNumberingCode = async () => { - const inputType = config.inputType || config.type || "text"; + // inputType을 여러 소스에서 확인 (props에서 직접 전달받거나 config에서) + const inputType = (props as any).inputType || config.inputType || config.type || "text"; // numbering 타입이 아니면 스킵 if (inputType !== "numbering") { @@ -524,9 +536,12 @@ export const V2Input = forwardRef((props, ref) => } // detailSettings에서 numberingRuleId 추출 - if (targetColumn.detailSettings && typeof targetColumn.detailSettings === "string") { + if (targetColumn.detailSettings) { try { - const parsed = JSON.parse(targetColumn.detailSettings); + // 문자열이면 파싱, 객체면 그대로 사용 + const parsed = typeof targetColumn.detailSettings === "string" + ? JSON.parse(targetColumn.detailSettings) + : targetColumn.detailSettings; numberingRuleIdRef.current = parsed.numberingRuleId || null; // 🆕 채번 규칙 ID를 formData에 저장 (저장 시 allocateCode 호출을 위해) @@ -618,7 +633,7 @@ export const V2Input = forwardRef((props, ref) => // 타입별 입력 컴포넌트 렌더링 const renderInput = () => { - const inputType = config.inputType || config.type || "text"; + const inputType = propsInputType || config.inputType || config.type || "text"; switch (inputType) { case "text": return ( diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 8d6c639d..9571abef 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -506,10 +506,12 @@ export const DynamicComponentRenderer: React.FC = }; // 🆕 엔티티 검색 컴포넌트는 componentConfig.tableName을 사용해야 함 (화면 테이블이 아닌 검색 대상 테이블) + // 🆕 v2-input도 포함 (채번 규칙 조회 시 tableName 필요) const useConfigTableName = componentType === "entity-search-input" || componentType === "autocomplete-search-input" || - componentType === "modal-repeater-table"; + componentType === "modal-repeater-table" || + componentType === "v2-input"; const rendererProps = { component, @@ -524,9 +526,21 @@ export const DynamicComponentRenderer: React.FC = componentConfig: component.componentConfig, // componentConfig의 모든 속성을 props로 spread (tableName, displayField 등) ...(component.componentConfig || {}), + // 🆕 V2 레이아웃에서 overrides에서 복원된 상위 레벨 속성들도 전달 + inputType: (component as any).inputType || component.componentConfig?.inputType, + columnName: (component as any).columnName || component.componentConfig?.columnName, value: currentValue, // formData에서 추출한 현재 값 전달 // 새로운 기능들 전달 - autoGeneration: component.autoGeneration || component.componentConfig?.autoGeneration, + // 🆕 webTypeConfig.numberingRuleId가 있으면 autoGeneration으로 변환 + autoGeneration: component.autoGeneration || + component.componentConfig?.autoGeneration || + ((component as any).webTypeConfig?.numberingRuleId ? { + type: "numbering_rule" as const, + enabled: true, + options: { + numberingRuleId: (component as any).webTypeConfig.numberingRuleId, + }, + } : undefined), hidden: hiddenValue, // React 전용 props들은 직접 전달 (DOM에 전달되지 않음) isInteractive, @@ -534,7 +548,10 @@ export const DynamicComponentRenderer: React.FC = onFormDataChange, onChange: handleChange, // 개선된 onChange 핸들러 전달 // 🆕 엔티티 검색 컴포넌트는 componentConfig.tableName 유지, 그 외는 화면 테이블명 사용 - tableName: useConfigTableName ? component.componentConfig?.tableName || tableName : tableName, + // 🆕 component.tableName도 확인 (V2 레이아웃에서 overrides.tableName이 복원됨) + tableName: useConfigTableName + ? component.componentConfig?.tableName || (component as any).tableName || tableName + : tableName, menuId, // 🆕 메뉴 ID menuObjid, // 🆕 메뉴 OBJID (메뉴 스코프) selectedScreen, // 🆕 화면 정보 diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index d19885cb..b1d66eea 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -3043,8 +3043,12 @@ export class ButtonActionExecutor { } // 4. 모달 열기 이벤트 발생 - // passSelectedData가 true이면 editData로 전달 (수정 모드처럼 모든 필드 표시) + // 🔧 수정: openModalWithData는 "신규 등록 + 연결 데이터 전달"용이므로 + // editData가 아닌 splitPanelParentData로 전달해야 채번 등이 정상 작동함 const isPassDataMode = passSelectedData && selectedData.length > 0; + + // 🔧 isEditMode 옵션이 명시적으로 true인 경우에만 수정 모드로 처리 + const useAsEditData = config.isEditMode === true; const modalEvent = new CustomEvent("openScreenModal", { detail: { @@ -3054,9 +3058,10 @@ export class ButtonActionExecutor { size: config.modalSize || "md", selectedData: selectedData, selectedIds: selectedData.map((row: any) => row.id).filter(Boolean), - // 🆕 데이터 전달 모드일 때는 editData로 전달하여 모든 필드가 표시되도록 함 - editData: isPassDataMode ? parentData : undefined, - splitPanelParentData: isPassDataMode ? undefined : parentData, + // 🔧 수정: isEditMode가 명시적으로 true인 경우에만 editData로 전달 + // 기본적으로는 splitPanelParentData로 전달하여 신규 등록 + 연결 데이터 모드 + editData: useAsEditData && isPassDataMode ? parentData : undefined, + splitPanelParentData: isPassDataMode ? parentData : undefined, urlParams: dataSourceId ? { dataSourceId } : undefined, }, });