From aee4e86036794df63f6cb0c50bb974d5602164d2 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 17 Nov 2025 16:18:38 +0900 Subject: [PATCH 1/5] =?UTF-8?q?entity-search-input=20tableName=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-node/src/controllers/entitySearchController.ts | 9 +++++++++ .../components/entity-search-input/useEntitySearch.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend-node/src/controllers/entitySearchController.ts b/backend-node/src/controllers/entitySearchController.ts index 2b944e2b..5046d8bb 100644 --- a/backend-node/src/controllers/entitySearchController.ts +++ b/backend-node/src/controllers/entitySearchController.ts @@ -17,6 +17,15 @@ export async function searchEntity(req: Request, res: Response) { limit = "20", } = req.query; + // tableName 유효성 검증 + if (!tableName || tableName === "undefined" || tableName === "null") { + logger.warn("엔티티 검색 실패: 테이블명이 없음", { tableName }); + return res.status(400).json({ + success: false, + message: "테이블명이 지정되지 않았습니다. 컴포넌트 설정에서 sourceTable을 확인해주세요.", + }); + } + // 멀티테넌시 const companyCode = req.user!.companyCode; diff --git a/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts b/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts index 04428a55..1fac26d6 100644 --- a/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts +++ b/frontend/lib/registry/components/entity-search-input/useEntitySearch.ts @@ -34,6 +34,13 @@ export function useEntitySearch({ const search = useCallback( async (text: string, page: number = 1) => { + // tableName 유효성 검증 + if (!tableName || tableName === "undefined" || tableName === "null") { + console.warn("엔티티 검색 건너뜀: tableName이 없음", { tableName }); + setError("테이블명이 설정되지 않았습니다. 컴포넌트 설정을 확인해주세요."); + return; + } + try { setLoading(true); setError(null); @@ -60,7 +67,8 @@ export function useEntitySearch({ } } catch (err: any) { console.error("Entity search error:", err); - setError(err.response?.data?.message || "검색 중 오류가 발생했습니다"); + const errorMessage = err.response?.data?.message || "검색 중 오류가 발생했습니다"; + setError(errorMessage); } finally { setLoading(false); } From 23cd677413b30575b89605fa71a86b41e0c2be66 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 17 Nov 2025 16:48:42 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20entity-search=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컴포넌트 등록을 useEffect → 즉시 실행으로 변경 (3개 컴포넌트) - entity-search-input tableName 검증 추가 (FE/BE) - DynamicComponentRenderer에서 componentConfig props 전달 수정 - EntitySearchModal key prop 경고 해결 - 불필요한 ScreenDesigner 렌더링 코드 제거 Fixes: 컴포넌트 미등록 에러, tableName undefined 500 에러, React key 경고 --- .../src/controllers/entitySearchController.ts | 9 +++++++ frontend/components/screen/ScreenDesigner.tsx | 9 ------- .../table-category/CategoryValueAddDialog.tsx | 10 +++---- .../lib/registry/DynamicComponentRenderer.tsx | 2 ++ .../AutocompleteSearchInputRenderer.tsx | 16 +++-------- .../EntitySearchInputRenderer.tsx | 16 +++-------- .../entity-search-input/EntitySearchModal.tsx | 27 ++++++++++--------- .../entity-search-input/useEntitySearch.ts | 7 +++++ frontend/lib/registry/components/index.ts | 6 ++--- .../ModalRepeaterTableRenderer.tsx | 16 +++-------- 10 files changed, 50 insertions(+), 68 deletions(-) diff --git a/backend-node/src/controllers/entitySearchController.ts b/backend-node/src/controllers/entitySearchController.ts index 2b944e2b..5046d8bb 100644 --- a/backend-node/src/controllers/entitySearchController.ts +++ b/backend-node/src/controllers/entitySearchController.ts @@ -17,6 +17,15 @@ export async function searchEntity(req: Request, res: Response) { limit = "20", } = req.query; + // tableName 유효성 검증 + if (!tableName || tableName === "undefined" || tableName === "null") { + logger.warn("엔티티 검색 실패: 테이블명이 없음", { tableName }); + return res.status(400).json({ + success: false, + message: "테이블명이 지정되지 않았습니다. 컴포넌트 설정에서 sourceTable을 확인해주세요.", + }); + } + // 멀티테넌시 const companyCode = req.user!.companyCode; diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index aa87e83f..dcd80a62 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -70,9 +70,6 @@ import { toast } from "sonner"; import { MenuAssignmentModal } from "./MenuAssignmentModal"; import { FileAttachmentDetailModal } from "./FileAttachmentDetailModal"; import { initializeComponents } from "@/lib/registry/components"; -import { AutocompleteSearchInputRenderer } from "@/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputRenderer"; -import { EntitySearchInputRenderer } from "@/lib/registry/components/entity-search-input/EntitySearchInputRenderer"; -import { ModalRepeaterTableRenderer } from "@/lib/registry/components/modal-repeater-table/ModalRepeaterTableRenderer"; import { ScreenFileAPI } from "@/lib/api/screenFile"; import { safeMigrateLayout, needsMigration } from "@/lib/utils/widthToColumnSpan"; @@ -4967,12 +4964,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD )} - {/* 숨겨진 컴포넌트 렌더러들 (레지스트리 등록용) */} -
- - - -
); } diff --git a/frontend/components/table-category/CategoryValueAddDialog.tsx b/frontend/components/table-category/CategoryValueAddDialog.tsx index ae47cf44..c486cc1d 100644 --- a/frontend/components/table-category/CategoryValueAddDialog.tsx +++ b/frontend/components/table-category/CategoryValueAddDialog.tsx @@ -79,14 +79,14 @@ export const CategoryValueAddDialog: React.FC< const valueCode = generateCode(valueLabel); onAdd({ - tableName: "", - columnName: "", + tableName: "", // CategoryValueManager에서 오버라이드됨 + columnName: "", // CategoryValueManager에서 오버라이드됨 valueCode, valueLabel: valueLabel.trim(), - description: description.trim(), - color: color, + description: description.trim() || undefined, // 빈 문자열 대신 undefined + color: color === "none" ? undefined : color, // "none"은 undefined로 isDefault: false, - }); + } as TableCategoryValue); // 초기화 setValueLabel(""); diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 144b1f1a..865a361f 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -308,6 +308,8 @@ export const DynamicComponentRenderer: React.FC = style: finalStyle, // size를 포함한 최종 style config: component.componentConfig, componentConfig: component.componentConfig, + // componentConfig의 모든 속성을 props로 spread (tableName, displayField 등) + ...(component.componentConfig || {}), value: currentValue, // formData에서 추출한 현재 값 전달 // 새로운 기능들 전달 autoGeneration: component.autoGeneration || component.componentConfig?.autoGeneration, diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputRenderer.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputRenderer.tsx index 82691247..4829ae77 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputRenderer.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputRenderer.tsx @@ -1,19 +1,9 @@ "use client"; -import React, { useEffect } from "react"; import { ComponentRegistry } from "../../ComponentRegistry"; import { AutocompleteSearchInputDefinition } from "./index"; -export function AutocompleteSearchInputRenderer() { - useEffect(() => { - ComponentRegistry.registerComponent(AutocompleteSearchInputDefinition); - console.log("✅ AutocompleteSearchInput 컴포넌트 등록 완료"); - - return () => { - // 컴포넌트 언마운트 시 해제하지 않음 (싱글톤 패턴) - }; - }, []); - - return null; -} +// 파일 로드 시 즉시 등록 +ComponentRegistry.registerComponent(AutocompleteSearchInputDefinition); +console.log("✅ AutocompleteSearchInput 컴포넌트 등록 완료"); diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchInputRenderer.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchInputRenderer.tsx index fca4afd2..1b9d4f88 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchInputRenderer.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchInputRenderer.tsx @@ -1,19 +1,9 @@ "use client"; -import React, { useEffect } from "react"; import { ComponentRegistry } from "../../ComponentRegistry"; import { EntitySearchInputDefinition } from "./index"; -export function EntitySearchInputRenderer() { - useEffect(() => { - ComponentRegistry.registerComponent(EntitySearchInputDefinition); - console.log("✅ EntitySearchInput 컴포넌트 등록 완료"); - - return () => { - // 컴포넌트 언마운트 시 해제하지 않음 (싱글톤 패턴) - }; - }, []); - - return null; -} +// 파일 로드 시 즉시 등록 +ComponentRegistry.registerComponent(EntitySearchInputDefinition); +console.log("✅ EntitySearchInput 컴포넌트 등록 완료"); diff --git a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx index 03648f8f..a31c6cab 100644 --- a/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx +++ b/frontend/lib/registry/components/entity-search-input/EntitySearchModal.tsx @@ -155,17 +155,19 @@ export function EntitySearchModal({ ) : ( - results.map((item, index) => ( - handleSelect(item)} - > - {displayColumns.map((col, colIndex) => ( - - {item[col] || "-"} - - ))} + results.map((item, index) => { + const uniqueKey = item[valueField] !== undefined ? `${item[valueField]}` : `row-${index}`; + return ( + handleSelect(item)} + > + {displayColumns.map((col) => ( + + {item[col] || "-"} + + ))}