2025-09-26 01:28:51 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
import React, { useEffect } from "react";
|
2025-09-26 01:28:51 +09:00
|
|
|
import { Card } from "@/components/ui/card";
|
2025-09-26 17:11:18 +09:00
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Globe } from "lucide-react";
|
2025-09-26 01:28:51 +09:00
|
|
|
|
|
|
|
|
// 타입 import
|
|
|
|
|
import { RightPanelProps } from "../types/redesigned";
|
|
|
|
|
|
|
|
|
|
// 컴포넌트 import
|
|
|
|
|
import StepProgress from "./StepProgress";
|
|
|
|
|
import ConnectionStep from "./ConnectionStep";
|
|
|
|
|
import TableStep from "./TableStep";
|
|
|
|
|
import FieldMappingStep from "./FieldMappingStep";
|
|
|
|
|
import ControlConditionStep from "./ControlConditionStep";
|
|
|
|
|
import ActionConfigStep from "./ActionConfigStep";
|
|
|
|
|
import MultiActionConfigStep from "./MultiActionConfigStep";
|
2025-09-26 17:11:18 +09:00
|
|
|
import ExternalCallPanel from "../../../external-call/ExternalCallPanel";
|
2025-09-26 01:28:51 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🎯 우측 패널 (70% 너비)
|
|
|
|
|
* - 단계별 진행 UI
|
|
|
|
|
* - 연결 → 테이블 → 필드 매핑
|
|
|
|
|
* - 시각적 매핑 영역
|
|
|
|
|
*/
|
|
|
|
|
const RightPanel: React.FC<RightPanelProps> = ({ state, actions }) => {
|
2025-09-26 17:11:18 +09:00
|
|
|
console.log("🔄 [RightPanel] 컴포넌트 렌더링 - connectionType:", state.connectionType);
|
|
|
|
|
|
|
|
|
|
// connectionType 변경 감지
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("🔄 [RightPanel] connectionType 변경됨:", state.connectionType);
|
|
|
|
|
}, [state.connectionType]);
|
2025-09-26 01:28:51 +09:00
|
|
|
// 완료된 단계 계산
|
|
|
|
|
const completedSteps: number[] = [];
|
|
|
|
|
|
|
|
|
|
if (state.fromConnection && state.toConnection) {
|
|
|
|
|
completedSteps.push(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state.fromTable && state.toTable) {
|
|
|
|
|
completedSteps.push(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 새로운 단계 순서에 따른 완료 조건
|
|
|
|
|
const needsFieldMapping = state.actionType === "insert" || state.actionType === "upsert";
|
|
|
|
|
|
|
|
|
|
// 3단계: 제어 조건 (테이블 선택 후 바로 접근 가능)
|
|
|
|
|
if (state.fromTable && state.toTable) {
|
|
|
|
|
completedSteps.push(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4단계: 액션 설정
|
|
|
|
|
if (state.actionType) {
|
|
|
|
|
completedSteps.push(4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5단계: 컬럼 매핑 (INSERT/UPSERT인 경우에만)
|
|
|
|
|
if (needsFieldMapping && state.fieldMappings.length > 0) {
|
|
|
|
|
completedSteps.push(5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const renderCurrentStep = () => {
|
2025-09-26 17:11:18 +09:00
|
|
|
try {
|
|
|
|
|
// 외부호출인 경우 단계 무시하고 바로 외부호출 설정 화면 표시
|
|
|
|
|
console.log("🔍 [RightPanel] renderCurrentStep - connectionType:", state.connectionType);
|
|
|
|
|
if (state.connectionType === "external_call") {
|
|
|
|
|
console.log("✅ [RightPanel] 외부호출 화면 렌더링");
|
2025-09-26 01:28:51 +09:00
|
|
|
return (
|
2025-09-26 17:11:18 +09:00
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
<div className="flex-shrink-0 px-4 py-2">
|
|
|
|
|
<div className="flex items-center gap-3 border-b pb-2">
|
|
|
|
|
<Globe className="h-5 w-5 text-blue-600" />
|
|
|
|
|
<h2 className="text-lg font-semibold">외부 호출 설정</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-muted-foreground mt-1 text-sm">
|
|
|
|
|
REST API 호출을 통해 외부 시스템에 데이터를 전송하거나 알림을 보낼 수 있습니다.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-09-26 01:28:51 +09:00
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
{/* 관계명 및 설명 입력 */}
|
|
|
|
|
<div className="flex-shrink-0 px-4 pb-2">
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium">관계명 *</label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={state.relationshipName || ""}
|
|
|
|
|
onChange={(e) => actions.setRelationshipName(e.target.value)}
|
|
|
|
|
placeholder="외부호출 관계의 이름을 입력하세요"
|
|
|
|
|
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-sm font-medium">설명</label>
|
|
|
|
|
<textarea
|
|
|
|
|
value={state.description || ""}
|
|
|
|
|
onChange={(e) => actions.setDescription(e.target.value)}
|
|
|
|
|
placeholder="외부호출의 용도나 설명을 입력하세요"
|
|
|
|
|
rows={2}
|
|
|
|
|
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-26 01:28:51 +09:00
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
{/* 외부호출 패널 - 공간 최적화 */}
|
|
|
|
|
<div className="flex-1 overflow-hidden px-4">
|
|
|
|
|
<div className="h-full max-h-[calc(100vh-400px)] overflow-y-auto">
|
|
|
|
|
<ExternalCallPanel
|
|
|
|
|
relationshipId={`external-call-${Date.now()}`}
|
|
|
|
|
readonly={false}
|
|
|
|
|
initialSettings={
|
|
|
|
|
state.externalCallConfig || {
|
|
|
|
|
callType: "rest-api",
|
|
|
|
|
restApiSettings: {
|
|
|
|
|
apiUrl: "",
|
|
|
|
|
httpMethod: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
bodyTemplate: "",
|
|
|
|
|
authentication: { type: "none" },
|
|
|
|
|
timeout: 30000,
|
|
|
|
|
retryCount: 3,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onSettingsChange={actions.updateExternalCallConfig}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-26 01:28:51 +09:00
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
{/* 하단 버튼 - 바로 붙여서 고정 */}
|
|
|
|
|
<div className="flex-shrink-0 border-t bg-white px-4 py-2">
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={actions.saveMappings}
|
|
|
|
|
className="w-full bg-blue-600 hover:bg-blue-700"
|
|
|
|
|
disabled={state.isLoading}
|
|
|
|
|
>
|
|
|
|
|
{state.isLoading ? "저장 중..." : "저장"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-26 01:28:51 +09:00
|
|
|
);
|
2025-09-26 17:11:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 데이터 저장인 경우에만 단계별 진행
|
|
|
|
|
switch (state.currentStep) {
|
|
|
|
|
case 1:
|
|
|
|
|
return (
|
|
|
|
|
<ConnectionStep
|
|
|
|
|
connectionType={state.connectionType}
|
|
|
|
|
fromConnection={state.fromConnection}
|
|
|
|
|
toConnection={state.toConnection}
|
|
|
|
|
relationshipName={state.relationshipName}
|
|
|
|
|
description={state.description}
|
|
|
|
|
diagramId={state.diagramId} // 🔧 수정 모드 감지용
|
|
|
|
|
onSelectConnection={actions.selectConnection}
|
|
|
|
|
onSetRelationshipName={actions.setRelationshipName}
|
|
|
|
|
onSetDescription={actions.setDescription}
|
|
|
|
|
onNext={() => actions.goToStep(2)}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2025-09-26 01:28:51 +09:00
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
case 2:
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<TableStep
|
|
|
|
|
fromConnection={state.fromConnection}
|
|
|
|
|
toConnection={state.toConnection}
|
|
|
|
|
fromTable={state.fromTable}
|
|
|
|
|
toTable={state.toTable}
|
|
|
|
|
onSelectTable={actions.selectTable}
|
|
|
|
|
onNext={() => actions.goToStep(3)} // 3단계(제어 조건)로
|
|
|
|
|
onBack={() => actions.goToStep(1)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
// 데이터 저장인 경우 제어 조건 단계
|
|
|
|
|
return (
|
|
|
|
|
<ControlConditionStep
|
|
|
|
|
state={state}
|
|
|
|
|
actions={actions}
|
|
|
|
|
onBack={() => actions.goToStep(2)}
|
|
|
|
|
onNext={() => {
|
|
|
|
|
// 4단계로 넘어가기 전에 컬럼 로드
|
|
|
|
|
actions.loadColumns();
|
|
|
|
|
actions.goToStep(4);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
// 외부호출인 경우 4단계 없음
|
|
|
|
|
if (state.connectionType === "external_call") {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4단계: 통합된 멀티 액션 설정 (제어 조건 + 액션 설정 + 컬럼 매핑)
|
|
|
|
|
return (
|
|
|
|
|
<MultiActionConfigStep
|
|
|
|
|
fromTable={state.fromTable}
|
|
|
|
|
toTable={state.toTable}
|
|
|
|
|
fromConnection={state.fromConnection}
|
|
|
|
|
toConnection={state.toConnection}
|
|
|
|
|
fromColumns={state.fromColumns} // 🔧 중앙에서 관리되는 컬럼 정보
|
|
|
|
|
toColumns={state.toColumns} // 🔧 중앙에서 관리되는 컬럼 정보
|
|
|
|
|
controlConditions={state.controlConditions}
|
|
|
|
|
onUpdateControlCondition={actions.updateControlCondition}
|
|
|
|
|
onDeleteControlCondition={actions.deleteControlCondition}
|
|
|
|
|
onAddControlCondition={actions.addControlCondition}
|
|
|
|
|
actionGroups={state.actionGroups}
|
|
|
|
|
groupsLogicalOperator={state.groupsLogicalOperator}
|
|
|
|
|
onUpdateActionGroup={actions.updateActionGroup}
|
|
|
|
|
onDeleteActionGroup={actions.deleteActionGroup}
|
|
|
|
|
onAddActionGroup={actions.addActionGroup}
|
|
|
|
|
onAddActionToGroup={actions.addActionToGroup}
|
|
|
|
|
onUpdateActionInGroup={actions.updateActionInGroup}
|
|
|
|
|
onDeleteActionFromGroup={actions.deleteActionFromGroup}
|
|
|
|
|
onSetGroupsLogicalOperator={actions.setGroupsLogicalOperator}
|
|
|
|
|
fieldMappings={state.fieldMappings}
|
|
|
|
|
onCreateMapping={actions.createMapping}
|
|
|
|
|
onDeleteMapping={actions.deleteMapping}
|
|
|
|
|
onLoadColumns={actions.loadColumns}
|
|
|
|
|
onNext={() => {
|
|
|
|
|
// 완료 처리 - 저장 및 상위 컴포넌트 알림
|
|
|
|
|
actions.saveMappings();
|
|
|
|
|
}}
|
|
|
|
|
onBack={() => actions.goToStep(3)}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("❌ [RightPanel] renderCurrentStep 에러:", error);
|
|
|
|
|
return <div>renderCurrentStep 에러: {String(error)}</div>;
|
2025-09-26 01:28:51 +09:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
try {
|
|
|
|
|
console.log("🎯 [RightPanel] return 시작 - connectionType:", state.connectionType);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
{/* 단계 진행 표시 - 외부호출이 아닐 때만 */}
|
|
|
|
|
{state.connectionType !== "external_call" && (
|
|
|
|
|
<div className="bg-card/50 border-b p-3">
|
|
|
|
|
<StepProgress
|
|
|
|
|
currentStep={state.currentStep}
|
|
|
|
|
completedSteps={completedSteps}
|
|
|
|
|
onStepClick={actions.goToStep}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-26 01:28:51 +09:00
|
|
|
|
2025-09-26 17:11:18 +09:00
|
|
|
{/* 현재 단계 컨텐츠 */}
|
|
|
|
|
<div className="min-h-0 flex-1 p-3">
|
|
|
|
|
{(() => {
|
|
|
|
|
console.log("🎯 [RightPanel] 조건부 렌더링 체크 - connectionType:", state.connectionType);
|
|
|
|
|
console.log("🎯 [RightPanel] external_call인가?", state.connectionType === "external_call");
|
|
|
|
|
|
|
|
|
|
if (state.connectionType === "external_call") {
|
|
|
|
|
console.log("🎯 [RightPanel] 외부호출 렌더링 시작");
|
|
|
|
|
return renderCurrentStep();
|
|
|
|
|
} else {
|
|
|
|
|
console.log("🎯 [RightPanel] 데이터 저장 렌더링 시작");
|
|
|
|
|
return <Card className="flex h-full flex-col overflow-hidden">{renderCurrentStep()}</Card>;
|
|
|
|
|
}
|
|
|
|
|
})()}
|
|
|
|
|
</div>
|
2025-09-26 01:28:51 +09:00
|
|
|
</div>
|
2025-09-26 17:11:18 +09:00
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("❌ [RightPanel] 렌더링 에러:", error);
|
|
|
|
|
return <div>렌더링 에러 발생: {String(error)}</div>;
|
|
|
|
|
}
|
2025-09-26 01:28:51 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default RightPanel;
|