Compare commits

..

5 Commits

Author SHA1 Message Date
kjs dc7e7714f7 Merge branch 'feature/screen-management' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management 2025-10-31 17:28:03 +09:00
kjs e42675616b fix: 제어관리 저장 및 실행 문제 수정
- frontend: screen.ts에 saveScreenLayout 함수 추가 (ScreenDesigner_new.tsx가 호출하던 누락된 함수)
- frontend: ScreenDesigner_new.tsx 저장 시 디버깅 로그 추가
- backend: screenManagementService.ts에 dataflowConfig 저장 확인 로그 추가

문제 원인:
- ScreenDesigner_new.tsx가 호출하던 screenApi.saveScreenLayout 함수가 정의되지 않음
- 이로 인해 레이아웃 저장이 실패했을 가능성

해결:
- saveScreenLayout 함수를 추가하여 정상적인 레이아웃 저장 가능
- 디버깅 로그를 통해 실제로 selectedDiagramId가 저장되는지 확인 가능
2025-10-31 17:21:47 +09:00
kjs 9a674b6686 fix: 버튼 제어관리 노드 플로우 실행 수정
프론트엔드:
- ImprovedButtonControlConfigPanel에서 selectedDiagramId 저장 추가
- 플로우 선택 시 flowConfig와 함께 selectedDiagramId도 저장
- selectedRelationshipId는 null로 설정 (노드 플로우는 관계 불필요)

백엔드:
- dynamicFormService에서 relationshipId 유무에 따라 실행 방식 분기
- relationshipId가 없으면 NodeFlowExecutionService.executeFlow() 실행
- relationshipId가 있으면 기존 dataflowControlService.executeDataflowControl() 실행
- 노드 플로우 실행 시 formData를 contextData로 전달

원인:
- 기존에는 flowConfig만 저장하고 selectedDiagramId를 저장하지 않음
- 백엔드에서 selectedDiagramId가 없어서 제어관리 실행 조건 불만족
- 관계 기반 제어와 노드 플로우를 구분하지 못함
2025-10-31 17:16:47 +09:00
kjs 27d278ca8c debug: 제어관리 실행 디버깅 로그 추가
- 제어관리가 실행되지 않는 원인을 파악하기 위한 상세 로그 추가
- 각 컴포넌트의 타입, 액션, 제어관리 설정 여부 출력
- 제어관리 설정이 없는 경우 명시적인 로그 출력
- 조건 불만족 시 어떤 조건이 맞지 않는지 확인 가능
2025-10-31 17:12:29 +09:00
kjs 3d6ce26f9d feat: 테이블 리스트 컴포넌트 제목 편집 기능 추가
- TableListConfigPanel에 테이블 제목 입력 필드 추가
- 제목 표시 우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명
- 사용자가 제목을 비워두면 자동으로 테이블 라벨명 또는 테이블명 표시
- 화면 편집기에서 테이블 제목을 자유롭게 수정 가능
2025-10-31 11:10:09 +09:00
7 changed files with 112 additions and 10 deletions

View File

@ -1355,9 +1355,20 @@ export class DynamicFormService {
console.log(`📋 화면 컴포넌트 조회 결과:`, screenLayouts.length);
// 저장 버튼 중에서 제어관리가 활성화된 것 찾기
let controlConfigFound = false;
for (const layout of screenLayouts) {
const properties = layout.properties as any;
// 디버깅: 모든 컴포넌트 정보 출력
console.log(`🔍 컴포넌트 검사:`, {
componentId: layout.component_id,
componentType: properties?.componentType,
actionType: properties?.componentConfig?.action?.type,
enableDataflowControl: properties?.webTypeConfig?.enableDataflowControl,
hasDataflowConfig: !!properties?.webTypeConfig?.dataflowConfig,
hasDiagramId: !!properties?.webTypeConfig?.dataflowConfig?.selectedDiagramId,
});
// 버튼 컴포넌트이고 저장 액션이며 제어관리가 활성화된 경우
if (
properties?.componentType === "button-primary" &&
@ -1365,6 +1376,7 @@ export class DynamicFormService {
properties?.webTypeConfig?.enableDataflowControl === true &&
properties?.webTypeConfig?.dataflowConfig?.selectedDiagramId
) {
controlConfigFound = true;
const diagramId =
properties.webTypeConfig.dataflowConfig.selectedDiagramId;
const relationshipId =
@ -1377,9 +1389,37 @@ export class DynamicFormService {
triggerType,
});
// 제어관리 실행
const controlResult =
await this.dataflowControlService.executeDataflowControl(
// 노드 플로우 실행 (relationshipId가 없는 경우 노드 플로우로 간주)
let controlResult: any;
if (!relationshipId) {
// 노드 플로우 실행
console.log(`🚀 노드 플로우 실행 (flowId: ${diagramId})`);
const { NodeFlowExecutionService } = await import("./nodeFlowExecutionService");
const executionResult = await NodeFlowExecutionService.executeFlow(diagramId, {
sourceData: [savedData],
dataSourceType: "formData",
buttonId: "save-button",
screenId: screenId,
userId: userId,
formData: savedData,
});
controlResult = {
success: executionResult.success,
message: executionResult.message,
executedActions: executionResult.executedNodes?.map((node: any) => ({
nodeId: node.nodeId,
status: node.status,
duration: node.duration,
})),
errors: executionResult.errors,
};
} else {
// 관계 기반 제어관리 실행
console.log(`🎯 관계 기반 제어관리 실행 (relationshipId: ${relationshipId})`);
controlResult = await this.dataflowControlService.executeDataflowControl(
diagramId,
relationshipId,
triggerType,
@ -1387,6 +1427,7 @@ export class DynamicFormService {
tableName,
userId
);
}
console.log(`🎯 제어관리 실행 결과:`, controlResult);
@ -1417,6 +1458,10 @@ export class DynamicFormService {
break;
}
}
if (!controlConfigFound) {
console.log(` 제어관리 설정이 없습니다. (화면 ID: ${screenId})`);
}
} catch (error) {
console.error("❌ 제어관리 설정 확인 및 실행 오류:", error);
// 에러를 다시 던지지 않음 - 메인 저장 프로세스에 영향 주지 않기 위해

View File

@ -1278,6 +1278,11 @@ export class ScreenManagementService {
},
};
// 🔍 디버깅: webTypeConfig.dataflowConfig 확인
if ((component as any).webTypeConfig?.dataflowConfig) {
console.log(`🔍 컴포넌트 ${component.id}의 dataflowConfig:`, JSON.stringify((component as any).webTypeConfig.dataflowConfig, null, 2));
}
await query(
`INSERT INTO screen_layouts (
screen_id, component_type, component_id, parent_id,

View File

@ -263,6 +263,18 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
try {
setIsSaving(true);
// 🔍 디버깅: 저장할 레이아웃 데이터 확인
console.log("🔍 레이아웃 저장 요청:", {
screenId: selectedScreen.screenId,
componentsCount: layout.components.length,
components: layout.components.map(c => ({
id: c.id,
type: c.type,
webTypeConfig: (c as any).webTypeConfig,
})),
});
const response = await screenApi.saveScreenLayout(selectedScreen.screenId, layout);
if (response.success) {
toast.success("화면이 저장되었습니다.");

View File

@ -63,11 +63,17 @@ export const ImprovedButtonControlConfigPanel: React.FC<ImprovedButtonControlCon
const handleFlowSelect = (flowId: string) => {
const selectedFlow = flows.find((f) => f.flowId.toString() === flowId);
if (selectedFlow) {
onUpdateProperty("webTypeConfig.dataflowConfig.flowConfig", {
flowId: selectedFlow.flowId,
flowName: selectedFlow.flowName,
executionTiming: "before", // 기본값
contextData: {},
// 전체 dataflowConfig 업데이트 (selectedDiagramId 포함)
onUpdateProperty("webTypeConfig.dataflowConfig", {
...dataflowConfig,
selectedDiagramId: selectedFlow.flowId, // 백엔드에서 사용
selectedRelationshipId: null, // 노드 플로우는 관계 ID 불필요
flowConfig: {
flowId: selectedFlow.flowId,
flowName: selectedFlow.flowName,
executionTiming: "before", // 기본값
contextData: {},
},
});
}
};

View File

@ -151,6 +151,12 @@ export const screenApi = {
await apiClient.post(`/screen-management/screens/${screenId}/layout`, layoutData);
},
// 화면 레이아웃 저장 (ScreenDesigner_new.tsx용)
saveScreenLayout: async (screenId: number, layoutData: LayoutData): Promise<ApiResponse<void>> => {
const response = await apiClient.post(`/screen-management/screens/${screenId}/layout`, layoutData);
return response.data;
},
// 화면 레이아웃 조회
getLayout: async (screenId: number): Promise<LayoutData> => {
const response = await apiClient.get(`/screen-management/screens/${screenId}/layout`);

View File

@ -874,7 +874,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
<div {...domProps}>
{tableConfig.showHeader && (
<div className="px-4 py-3 border-b border-border sm:px-6 sm:py-4">
<h2 className="text-base font-semibold text-foreground sm:text-lg">{tableConfig.title || tableLabel}</h2>
<h2 className="text-base font-semibold text-foreground sm:text-lg">
{tableConfig.title || tableLabel || finalSelectedTable}
</h2>
</div>
)}
@ -936,7 +938,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
{/* 헤더 */}
{tableConfig.showHeader && (
<div className="px-4 py-3 border-b border-border flex-shrink-0 sm:px-6 sm:py-4">
<h2 className="text-base font-semibold text-foreground sm:text-lg">{tableConfig.title || tableLabel}</h2>
<h2 className="text-base font-semibold text-foreground sm:text-lg">
{tableConfig.title || tableLabel || finalSelectedTable}
</h2>
</div>
)}

View File

@ -727,6 +727,30 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
<div className="text-sm font-medium"> </div>
<div className="space-y-6">
{/* 테이블 제목 설정 */}
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold"> </h3>
</div>
<hr className="border-border" />
<div className="space-y-1">
<Label htmlFor="tableTitle" className="text-xs">
( )
</Label>
<Input
id="tableTitle"
type="text"
value={config.title || ""}
onChange={(e) => handleChange("title", e.target.value)}
placeholder="테이블 제목 입력..."
className="h-8 text-xs"
/>
<p className="text-[10px] text-muted-foreground">
우선순위: 사용자
</p>
</div>
</div>
{/* 가로 스크롤 및 컬럼 고정 */}
<div className="space-y-3">
<div>