diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 52ed357b..05e3afe9 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -1665,18 +1665,28 @@ export class ScreenManagementService { console.log(`V2 레이아웃 발견, V2 형식으로 반환`); const layoutData = v2Layout.layout_data; + // URL에서 컴포넌트 타입 추출하는 헬퍼 함수 + const getTypeFromUrl = (url: string | undefined): string => { + if (!url) return "component"; + const parts = url.split("/"); + return parts[parts.length - 1] || "component"; + }; + // V2 형식의 components를 LayoutData 형식으로 변환 - const components = (layoutData.components || []).map((comp: any) => ({ - id: comp.id, - type: comp.overrides?.type || "component", - position: comp.position || { x: 0, y: 0, z: 1 }, - size: comp.size || { width: 200, height: 100 }, - componentUrl: comp.url, - componentType: comp.overrides?.type, - componentConfig: comp.overrides || {}, - displayOrder: comp.displayOrder || 0, - ...comp.overrides, - })); + const components = (layoutData.components || []).map((comp: any) => { + const componentType = getTypeFromUrl(comp.url); + return { + id: comp.id, + type: componentType, + position: comp.position || { x: 0, y: 0, z: 1 }, + size: comp.size || { width: 200, height: 100 }, + componentUrl: comp.url, + componentType: componentType, + componentConfig: comp.overrides || {}, + displayOrder: comp.displayOrder || 0, + ...comp.overrides, + }; + }); // screenResolution이 없으면 컴포넌트 위치 기반으로 자동 계산 let screenResolution = layoutData.screenResolution; diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 23b684ac..d3c911ef 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -173,7 +173,15 @@ export const DynamicComponentRenderer: React.FC = ...props }) => { // 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용 - const rawComponentType = (component as any).componentType || component.type; + // 🆕 V2 레이아웃의 경우 url에서 컴포넌트 타입 추출 (예: "@/lib/registry/components/v2-input" → "v2-input") + const extractTypeFromUrl = (url: string | undefined): string | undefined => { + if (!url) return undefined; + // url의 마지막 세그먼트를 컴포넌트 타입으로 사용 + const segments = url.split("/"); + return segments[segments.length - 1]; + }; + + const rawComponentType = (component as any).componentType || component.type || extractTypeFromUrl((component as any).url); // 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지) const mapToV2ComponentType = (type: string | undefined): string | undefined => { diff --git a/frontend/lib/registry/components/index.ts b/frontend/lib/registry/components/index.ts index 8a2ac932..19f33cd1 100644 --- a/frontend/lib/registry/components/index.ts +++ b/frontend/lib/registry/components/index.ts @@ -106,6 +106,9 @@ import "./v2-table-search-widget"; import "./v2-tabs-widget/tabs-component"; import "./v2-category-manager/V2CategoryManagerRenderer"; import "./v2-media"; // 통합 미디어 컴포넌트 +import "./v2-input/V2InputRenderer"; // V2 통합 입력 컴포넌트 +import "./v2-select/V2SelectRenderer"; // V2 통합 선택 컴포넌트 +import "./v2-date/V2DateRenderer"; // V2 통합 날짜 컴포넌트 /** * 컴포넌트 초기화 함수 diff --git a/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx b/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx new file mode 100644 index 00000000..dfbbceb1 --- /dev/null +++ b/frontend/lib/registry/components/v2-date/V2DateRenderer.tsx @@ -0,0 +1,64 @@ +"use client"; + +import React from "react"; +import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; +import { V2DateDefinition } from "./index"; +import { V2Date } from "@/components/v2/V2Date"; + +/** + * V2Date 렌더러 + * 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록 + */ +export class V2DateRenderer extends AutoRegisteringComponentRenderer { + static componentDefinition = V2DateDefinition; + + render(): React.ReactElement { + const { component, formData, onFormDataChange, isDesignMode, isSelected, isInteractive, ...restProps } = this.props; + + // 컴포넌트 설정 추출 + const config = component.componentConfig || component.config || {}; + const columnName = component.columnName; + + // formData에서 현재 값 가져오기 + const currentValue = formData?.[columnName] ?? component.value ?? ""; + + // 값 변경 핸들러 + const handleChange = (value: any) => { + if (isInteractive && onFormDataChange && columnName) { + onFormDataChange(columnName, value); + } + }; + + return ( + + ); + } +} + +// 자동 등록 실행 +V2DateRenderer.registerSelf(); + +// Hot Reload 지원 (개발 모드) +if (process.env.NODE_ENV === "development") { + V2DateRenderer.enableHotReload(); +} diff --git a/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx new file mode 100644 index 00000000..1afc2075 --- /dev/null +++ b/frontend/lib/registry/components/v2-input/V2InputRenderer.tsx @@ -0,0 +1,72 @@ +"use client"; + +import React from "react"; +import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; +import { V2InputDefinition } from "./index"; +import { V2Input } from "@/components/v2/V2Input"; + +/** + * V2Input 렌더러 + * 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록 + */ +export class V2InputRenderer extends AutoRegisteringComponentRenderer { + static componentDefinition = V2InputDefinition; + + render(): React.ReactElement { + const { component, formData, onFormDataChange, isDesignMode, isSelected, isInteractive, ...restProps } = this.props; + + // 컴포넌트 설정 추출 + const config = component.componentConfig || component.config || {}; + const columnName = component.columnName; + const tableName = component.tableName || this.props.tableName; + + // formData에서 현재 값 가져오기 + const currentValue = formData?.[columnName] ?? component.value ?? ""; + + // 값 변경 핸들러 + const handleChange = (value: any) => { + if (isInteractive && onFormDataChange && columnName) { + onFormDataChange(columnName, value); + } + }; + + return ( + + ); + } +} + +// 자동 등록 실행 +V2InputRenderer.registerSelf(); + +// Hot Reload 지원 (개발 모드) +if (process.env.NODE_ENV === "development") { + V2InputRenderer.enableHotReload(); +} diff --git a/frontend/lib/registry/components/v2-select/V2SelectRenderer.tsx b/frontend/lib/registry/components/v2-select/V2SelectRenderer.tsx new file mode 100644 index 00000000..5fbdfcf7 --- /dev/null +++ b/frontend/lib/registry/components/v2-select/V2SelectRenderer.tsx @@ -0,0 +1,71 @@ +"use client"; + +import React from "react"; +import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; +import { V2SelectDefinition } from "./index"; +import { V2Select } from "@/components/v2/V2Select"; + +/** + * V2Select 렌더러 + * 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록 + */ +export class V2SelectRenderer extends AutoRegisteringComponentRenderer { + static componentDefinition = V2SelectDefinition; + + render(): React.ReactElement { + const { component, formData, onFormDataChange, isDesignMode, isSelected, isInteractive, ...restProps } = this.props; + + // 컴포넌트 설정 추출 + const config = component.componentConfig || component.config || {}; + const columnName = component.columnName; + const tableName = component.tableName || this.props.tableName; + + // formData에서 현재 값 가져오기 + const currentValue = formData?.[columnName] ?? component.value ?? ""; + + // 값 변경 핸들러 + const handleChange = (value: any) => { + if (isInteractive && onFormDataChange && columnName) { + onFormDataChange(columnName, value); + } + }; + + return ( + + ); + } +} + +// 자동 등록 실행 +V2SelectRenderer.registerSelf(); + +// Hot Reload 지원 (개발 모드) +if (process.env.NODE_ENV === "development") { + V2SelectRenderer.enableHotReload(); +}