fix: 컴포넌트 등록 및 entity-search 검증 개선
- 컴포넌트 등록을 useEffect → 즉시 실행으로 변경 (3개 컴포넌트) - entity-search-input tableName 검증 추가 (FE/BE) - DynamicComponentRenderer에서 componentConfig props 전달 수정 - EntitySearchModal key prop 경고 해결 - 불필요한 ScreenDesigner 렌더링 코드 제거 Fixes: 컴포넌트 미등록 에러, tableName undefined 500 에러, React key 경고
This commit is contained in:
parent
6839deac97
commit
23cd677413
|
|
@ -17,6 +17,15 @@ export async function searchEntity(req: Request, res: Response) {
|
||||||
limit = "20",
|
limit = "20",
|
||||||
} = req.query;
|
} = 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;
|
const companyCode = req.user!.companyCode;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,6 @@ import { toast } from "sonner";
|
||||||
import { MenuAssignmentModal } from "./MenuAssignmentModal";
|
import { MenuAssignmentModal } from "./MenuAssignmentModal";
|
||||||
import { FileAttachmentDetailModal } from "./FileAttachmentDetailModal";
|
import { FileAttachmentDetailModal } from "./FileAttachmentDetailModal";
|
||||||
import { initializeComponents } from "@/lib/registry/components";
|
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 { ScreenFileAPI } from "@/lib/api/screenFile";
|
||||||
import { safeMigrateLayout, needsMigration } from "@/lib/utils/widthToColumnSpan";
|
import { safeMigrateLayout, needsMigration } from "@/lib/utils/widthToColumnSpan";
|
||||||
|
|
||||||
|
|
@ -4967,12 +4964,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TableOptionsProvider>
|
</TableOptionsProvider>
|
||||||
{/* 숨겨진 컴포넌트 렌더러들 (레지스트리 등록용) */}
|
|
||||||
<div style={{ display: "none" }}>
|
|
||||||
<AutocompleteSearchInputRenderer />
|
|
||||||
<EntitySearchInputRenderer />
|
|
||||||
<ModalRepeaterTableRenderer />
|
|
||||||
</div>
|
|
||||||
</ScreenPreviewProvider>
|
</ScreenPreviewProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,14 +79,14 @@ export const CategoryValueAddDialog: React.FC<
|
||||||
const valueCode = generateCode(valueLabel);
|
const valueCode = generateCode(valueLabel);
|
||||||
|
|
||||||
onAdd({
|
onAdd({
|
||||||
tableName: "",
|
tableName: "", // CategoryValueManager에서 오버라이드됨
|
||||||
columnName: "",
|
columnName: "", // CategoryValueManager에서 오버라이드됨
|
||||||
valueCode,
|
valueCode,
|
||||||
valueLabel: valueLabel.trim(),
|
valueLabel: valueLabel.trim(),
|
||||||
description: description.trim(),
|
description: description.trim() || undefined, // 빈 문자열 대신 undefined
|
||||||
color: color,
|
color: color === "none" ? undefined : color, // "none"은 undefined로
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
});
|
} as TableCategoryValue);
|
||||||
|
|
||||||
// 초기화
|
// 초기화
|
||||||
setValueLabel("");
|
setValueLabel("");
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||||
style: finalStyle, // size를 포함한 최종 style
|
style: finalStyle, // size를 포함한 최종 style
|
||||||
config: component.componentConfig,
|
config: component.componentConfig,
|
||||||
componentConfig: component.componentConfig,
|
componentConfig: component.componentConfig,
|
||||||
|
// componentConfig의 모든 속성을 props로 spread (tableName, displayField 등)
|
||||||
|
...(component.componentConfig || {}),
|
||||||
value: currentValue, // formData에서 추출한 현재 값 전달
|
value: currentValue, // formData에서 추출한 현재 값 전달
|
||||||
// 새로운 기능들 전달
|
// 새로운 기능들 전달
|
||||||
autoGeneration: component.autoGeneration || component.componentConfig?.autoGeneration,
|
autoGeneration: component.autoGeneration || component.componentConfig?.autoGeneration,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { ComponentRegistry } from "../../ComponentRegistry";
|
import { ComponentRegistry } from "../../ComponentRegistry";
|
||||||
import { AutocompleteSearchInputDefinition } from "./index";
|
import { AutocompleteSearchInputDefinition } from "./index";
|
||||||
|
|
||||||
export function AutocompleteSearchInputRenderer() {
|
// 파일 로드 시 즉시 등록
|
||||||
useEffect(() => {
|
|
||||||
ComponentRegistry.registerComponent(AutocompleteSearchInputDefinition);
|
ComponentRegistry.registerComponent(AutocompleteSearchInputDefinition);
|
||||||
console.log("✅ AutocompleteSearchInput 컴포넌트 등록 완료");
|
console.log("✅ AutocompleteSearchInput 컴포넌트 등록 완료");
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 컴포넌트 언마운트 시 해제하지 않음 (싱글톤 패턴)
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { ComponentRegistry } from "../../ComponentRegistry";
|
import { ComponentRegistry } from "../../ComponentRegistry";
|
||||||
import { EntitySearchInputDefinition } from "./index";
|
import { EntitySearchInputDefinition } from "./index";
|
||||||
|
|
||||||
export function EntitySearchInputRenderer() {
|
// 파일 로드 시 즉시 등록
|
||||||
useEffect(() => {
|
|
||||||
ComponentRegistry.registerComponent(EntitySearchInputDefinition);
|
ComponentRegistry.registerComponent(EntitySearchInputDefinition);
|
||||||
console.log("✅ EntitySearchInput 컴포넌트 등록 완료");
|
console.log("✅ EntitySearchInput 컴포넌트 등록 완료");
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 컴포넌트 언마운트 시 해제하지 않음 (싱글톤 패턴)
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,14 +155,16 @@ export function EntitySearchModal({
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
results.map((item, index) => (
|
results.map((item, index) => {
|
||||||
|
const uniqueKey = item[valueField] !== undefined ? `${item[valueField]}` : `row-${index}`;
|
||||||
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={item[valueField] || index}
|
key={uniqueKey}
|
||||||
className="border-t hover:bg-accent cursor-pointer transition-colors"
|
className="border-t hover:bg-accent cursor-pointer transition-colors"
|
||||||
onClick={() => handleSelect(item)}
|
onClick={() => handleSelect(item)}
|
||||||
>
|
>
|
||||||
{displayColumns.map((col, colIndex) => (
|
{displayColumns.map((col) => (
|
||||||
<td key={`${item[valueField] || index}-${col}-${colIndex}`} className="px-4 py-2">
|
<td key={`${uniqueKey}-${col}`} className="px-4 py-2">
|
||||||
{item[col] || "-"}
|
{item[col] || "-"}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
@ -180,7 +182,8 @@ export function EntitySearchModal({
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
);
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,13 @@ export function useEntitySearch({
|
||||||
|
|
||||||
const search = useCallback(
|
const search = useCallback(
|
||||||
async (text: string, page: number = 1) => {
|
async (text: string, page: number = 1) => {
|
||||||
|
// tableName 유효성 검증
|
||||||
|
if (!tableName || tableName === "undefined" || tableName === "null") {
|
||||||
|
console.warn("엔티티 검색 건너뜀: tableName이 없음", { tableName });
|
||||||
|
setError("테이블명이 설정되지 않았습니다. 컴포넌트 설정을 확인해주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@ import "./table-search-widget"; // 🆕 테이블 검색 필터 위젯
|
||||||
import "./customer-item-mapping/CustomerItemMappingRenderer"; // 🆕 거래처별 품목정보
|
import "./customer-item-mapping/CustomerItemMappingRenderer"; // 🆕 거래처별 품목정보
|
||||||
|
|
||||||
// 🆕 수주 등록 관련 컴포넌트들
|
// 🆕 수주 등록 관련 컴포넌트들
|
||||||
import { AutocompleteSearchInputRenderer } from "./autocomplete-search-input/AutocompleteSearchInputRenderer";
|
import "./autocomplete-search-input/AutocompleteSearchInputRenderer";
|
||||||
import { EntitySearchInputRenderer } from "./entity-search-input/EntitySearchInputRenderer";
|
import "./entity-search-input/EntitySearchInputRenderer";
|
||||||
import { ModalRepeaterTableRenderer } from "./modal-repeater-table/ModalRepeaterTableRenderer";
|
import "./modal-repeater-table/ModalRepeaterTableRenderer";
|
||||||
import "./order-registration-modal/OrderRegistrationModalRenderer";
|
import "./order-registration-modal/OrderRegistrationModalRenderer";
|
||||||
|
|
||||||
// 🆕 조건부 컨테이너 컴포넌트
|
// 🆕 조건부 컨테이너 컴포넌트
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { ComponentRegistry } from "../../ComponentRegistry";
|
import { ComponentRegistry } from "../../ComponentRegistry";
|
||||||
import { ModalRepeaterTableDefinition } from "./index";
|
import { ModalRepeaterTableDefinition } from "./index";
|
||||||
|
|
||||||
export function ModalRepeaterTableRenderer() {
|
// 파일 로드 시 즉시 등록
|
||||||
useEffect(() => {
|
|
||||||
ComponentRegistry.registerComponent(ModalRepeaterTableDefinition);
|
ComponentRegistry.registerComponent(ModalRepeaterTableDefinition);
|
||||||
console.log("✅ ModalRepeaterTable 컴포넌트 등록 완료");
|
console.log("✅ ModalRepeaterTable 컴포넌트 등록 완료");
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 컴포넌트 언마운트 시 해제하지 않음 (싱글톤 패턴)
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue