feat: Docker 및 컴포넌트 최적화

- Docker Compose 설정에서 Node.js 메모리 제한을 8192MB로 증가시키고, Next.js telemetry를 비활성화하여 성능을 개선하였습니다.
- Next.js 구성에서 메모리 사용량 최적화를 위한 webpackMemoryOptimizations를 활성화하였습니다.
- ScreenModal 컴포넌트에서 overflow 속성을 조정하여 라벨이 잘리지 않도록 개선하였습니다.
- InteractiveScreenViewerDynamic 컴포넌트에서 라벨 표시 여부를 확인하는 로직을 추가하여 사용자 경험을 향상시켰습니다.
- RealtimePreviewDynamic 컴포넌트에서 라벨 표시 및 디버깅 로그를 추가하여 렌더링 과정을 추적할 수 있도록 하였습니다.
- ImprovedButtonControlConfigPanel에서 controlMode 설정을 추가하여 플로우 제어 기능을 개선하였습니다.
- V2PropertiesPanel에서 라벨 텍스트 및 표시 상태 업데이트 로직을 개선하여 일관성을 높였습니다.
- DynamicComponentRenderer에서 라벨 표시 로직을 개선하여 사용자 정의 스타일을 보다 효과적으로 적용할 수 있도록 하였습니다.
- layoutV2Converter에서 webTypeConfig를 병합하여 버튼 제어 기능과 플로우 가시성을 보존하였습니다.
This commit is contained in:
DDD1542
2026-02-04 18:01:20 +09:00
parent 593209e26e
commit 32139beebc
15 changed files with 295 additions and 130 deletions

View File

@@ -43,13 +43,20 @@ export interface ButtonExecutionResult {
}
interface ControlConfig {
type: "relationship";
relationshipConfig: {
type: "relationship" | "flow";
relationshipConfig?: {
relationshipId: string;
relationshipName: string;
executionTiming: "before" | "after" | "replace";
contextData?: Record<string, any>;
};
// 🆕 플로우 기반 제어 설정
flowConfig?: {
flowId: number;
flowName: string;
executionTiming: "before" | "after" | "replace";
contextData?: Record<string, any>;
};
}
interface ExecutionPlan {
@@ -163,15 +170,22 @@ export class ImprovedButtonActionExecutor {
return plan;
}
// enableDataflowControl 체크를 제거하고 dataflowConfig만 있으면 실행
// 🔧 controlMode가 없으면 flowConfig/relationshipConfig 존재 여부로 자동 판단
const effectiveControlMode = dataflowConfig.controlMode
|| (dataflowConfig.flowConfig ? "flow" : null)
|| (dataflowConfig.relationshipConfig ? "relationship" : null)
|| "none";
console.log("📋 실행 계획 생성:", {
controlMode: dataflowConfig.controlMode,
effectiveControlMode,
hasFlowConfig: !!dataflowConfig.flowConfig,
hasRelationshipConfig: !!dataflowConfig.relationshipConfig,
enableDataflowControl: buttonConfig.enableDataflowControl,
});
// 관계 기반 제어만 지원
if (dataflowConfig.controlMode === "relationship" && dataflowConfig.relationshipConfig) {
// 관계 기반 제어
if (effectiveControlMode === "relationship" && dataflowConfig.relationshipConfig) {
const control: ControlConfig = {
type: "relationship",
relationshipConfig: dataflowConfig.relationshipConfig,
@@ -191,11 +205,34 @@ export class ImprovedButtonActionExecutor {
}
}
// 🆕 플로우 기반 제어
if (effectiveControlMode === "flow" && dataflowConfig.flowConfig) {
const control: ControlConfig = {
type: "flow",
flowConfig: dataflowConfig.flowConfig,
};
console.log("📋 플로우 제어 설정:", dataflowConfig.flowConfig);
switch (dataflowConfig.flowConfig.executionTiming) {
case "before":
plan.beforeControls.push(control);
break;
case "after":
plan.afterControls.push(control);
break;
case "replace":
plan.afterControls.push(control);
plan.hasReplaceControl = true;
break;
}
}
return plan;
}
/**
* 🔥 제어 실행 (관계 또는 외부호출)
* 🔥 제어 실행 (관계 또는 플로우)
*/
private static async executeControls(
controls: ControlConfig[],
@@ -206,8 +243,16 @@ export class ImprovedButtonActionExecutor {
for (const control of controls) {
try {
// 관계 실행만 지원
const result = await this.executeRelationship(control.relationshipConfig, formData, context);
let result: ExecutionResult;
// 🆕 제어 타입에 따라 분기 처리
if (control.type === "flow" && control.flowConfig) {
result = await this.executeFlow(control.flowConfig, formData, context);
} else if (control.type === "relationship" && control.relationshipConfig) {
result = await this.executeRelationship(control.relationshipConfig, formData, context);
} else {
throw new Error(`지원하지 않는 제어 타입: ${control.type}`);
}
results.push(result);
@@ -215,7 +260,7 @@ export class ImprovedButtonActionExecutor {
if (!result.success) {
throw new Error(result.message);
}
} catch (error) {
} catch (error: any) {
console.error(`제어 실행 실패 (${control.type}):`, error);
results.push({
success: false,
@@ -230,6 +275,61 @@ export class ImprovedButtonActionExecutor {
return results;
}
/**
* 🆕 플로우 실행
*/
private static async executeFlow(
config: {
flowId: number;
flowName: string;
executionTiming: "before" | "after" | "replace";
contextData?: Record<string, any>;
},
formData: Record<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
const startTime = Date.now();
try {
console.log(`🔄 플로우 실행 시작: ${config.flowName} (ID: ${config.flowId})`);
// 플로우 실행 API 호출
const response = await apiClient.post(`/api/dataflow/node-flows/${config.flowId}/execute`, {
formData,
contextData: config.contextData || {},
selectedRows: context.selectedRows || [],
flowSelectedData: context.flowSelectedData || [],
screenId: context.screenId,
companyCode: context.companyCode,
userId: context.userId,
});
const executionTime = Date.now() - startTime;
if (response.data?.success) {
console.log(`✅ 플로우 실행 성공: ${config.flowName}`, response.data);
return {
success: true,
message: `플로우 "${config.flowName}" 실행 완료`,
executionTime,
data: response.data,
};
} else {
throw new Error(response.data?.message || "플로우 실행 실패");
}
} catch (error: any) {
const executionTime = Date.now() - startTime;
console.error(`❌ 플로우 실행 실패: ${config.flowName}`, error);
return {
success: false,
message: `플로우 "${config.flowName}" 실행 실패: ${error.message}`,
executionTime,
error: error.message,
};
}
}
/**
* 🔥 관계 실행
*/

View File

@@ -191,6 +191,8 @@ export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData |
autoFill: overrides.autoFill,
// 🆕 style 설정 복원 (라벨 텍스트, 라벨 스타일 등)
style: overrides.style || {},
// 🔧 webTypeConfig 복원 (버튼 제어기능, 플로우 가시성 등)
webTypeConfig: overrides.webTypeConfig || {},
// 기존 구조 호환을 위한 추가 필드
parentId: null,
gridColumns: 12,
@@ -245,13 +247,47 @@ export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
if (comp.autoFill) topLevelProps.autoFill = comp.autoFill;
// 🆕 style 설정 저장 (라벨 텍스트, 라벨 스타일 등)
if (comp.style && Object.keys(comp.style).length > 0) topLevelProps.style = comp.style;
// 🔧 webTypeConfig 저장 (버튼 제어기능, 플로우 가시성 등)
if (comp.webTypeConfig && Object.keys(comp.webTypeConfig).length > 0) {
topLevelProps.webTypeConfig = comp.webTypeConfig;
// 🔍 디버그: webTypeConfig 저장 확인
if (comp.webTypeConfig.dataflowConfig || comp.webTypeConfig.enableDataflowControl) {
console.log("💾 webTypeConfig 저장:", {
componentId: comp.id,
enableDataflowControl: comp.webTypeConfig.enableDataflowControl,
dataflowConfig: comp.webTypeConfig.dataflowConfig,
});
}
}
// 현재 설정에서 차이값만 추출
const fullConfig = comp.componentConfig || {};
const configOverrides = extractCustomConfig(fullConfig, defaults);
// 🔧 디버그: style 저장 확인 (주석 처리)
// if (comp.style?.labelDisplay !== undefined || configOverrides.style?.labelDisplay !== undefined) { console.log("💾 저장 시 style 변환:", { componentId: comp.id, "comp.style": comp.style, "configOverrides.style": configOverrides.style, "topLevelProps.style": topLevelProps.style }); }
// 상위 레벨 속성과 componentConfig 병합
const overrides = { ...topLevelProps, ...configOverrides };
// 🔧 style은 양쪽을 병합하되 comp.style(topLevelProps.style)을 우선시
const mergedStyle = {
...(configOverrides.style || {}),
...(topLevelProps.style || {}),
};
// 🔧 webTypeConfig도 병합 (topLevelProps가 우선, dataflowConfig 등 보존)
const mergedWebTypeConfig = {
...(configOverrides.webTypeConfig || {}),
...(topLevelProps.webTypeConfig || {}),
};
const overrides = {
...topLevelProps,
...configOverrides,
// 🆕 병합된 style 사용 (comp.style 값이 최종 우선)
...(Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {}),
// 🆕 병합된 webTypeConfig 사용 (comp.webTypeConfig가 최종 우선)
...(Object.keys(mergedWebTypeConfig).length > 0 ? { webTypeConfig: mergedWebTypeConfig } : {}),
};
return {
id: comp.id,