Fix: DataConnectionDesigner.tsx 문법 오류 수정 - 손상된 파일 복구
This commit is contained in:
parent
1760703150
commit
68308efd22
|
|
@ -25,699 +25,11 @@ const initialState: DataConnectionState = {
|
||||||
},
|
},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
validationErrors: [],
|
validationErrors: [],
|
||||||
|
};
|
||||||
|
|
||||||
// 컬럼 정보 초기화
|
export const DataConnectionDesigner: React.FC = () => {
|
||||||
fromColumns: [],
|
const [state, setState] = useState<DataConnectionState>(initialState);
|
||||||
toColumns: [],
|
const { isMobile, isTablet } = useResponsive();
|
||||||
...initialData,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 🔧 수정 모드 감지 (initialData에 diagramId가 있으면 수정 모드)
|
|
||||||
const diagramId = initialData?.diagramId;
|
|
||||||
|
|
||||||
// 🔄 초기 데이터 로드
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialData && Object.keys(initialData).length > 1) {
|
|
||||||
console.log("🔄 초기 데이터 로드:", initialData);
|
|
||||||
|
|
||||||
// 로드된 데이터로 state 업데이트
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
connectionType: initialData.connectionType || prev.connectionType,
|
|
||||||
|
|
||||||
// 🔧 관계 정보 로드
|
|
||||||
relationshipName: initialData.relationshipName || prev.relationshipName,
|
|
||||||
description: initialData.description || prev.description,
|
|
||||||
groupsLogicalOperator: initialData.groupsLogicalOperator || prev.groupsLogicalOperator,
|
|
||||||
|
|
||||||
fromConnection: initialData.fromConnection || prev.fromConnection,
|
|
||||||
toConnection: initialData.toConnection || prev.toConnection,
|
|
||||||
fromTable: initialData.fromTable || prev.fromTable,
|
|
||||||
toTable: initialData.toTable || prev.toTable,
|
|
||||||
controlConditions: initialData.controlConditions || prev.controlConditions,
|
|
||||||
fieldMappings: initialData.fieldMappings || prev.fieldMappings,
|
|
||||||
|
|
||||||
// 🔧 외부호출 설정 로드
|
|
||||||
externalCallConfig: initialData.externalCallConfig || prev.externalCallConfig,
|
|
||||||
|
|
||||||
// 🔧 액션 그룹 데이터 로드 (기존 호환성 포함)
|
|
||||||
actionGroups:
|
|
||||||
initialData.actionGroups ||
|
|
||||||
// 기존 단일 액션 데이터를 그룹으로 변환
|
|
||||||
(initialData.actionType || initialData.actionConditions
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: "group_1",
|
|
||||||
name: "기본 액션 그룹",
|
|
||||||
logicalOperator: "AND" as const,
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
id: "action_1",
|
|
||||||
name: "액션 1",
|
|
||||||
actionType: initialData.actionType || ("insert" as const),
|
|
||||||
conditions: initialData.actionConditions || [],
|
|
||||||
fieldMappings: initialData.actionFieldMappings || [],
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: prev.actionGroups),
|
|
||||||
|
|
||||||
// 기존 호환성 필드들
|
|
||||||
actionType: initialData.actionType || prev.actionType,
|
|
||||||
actionConditions: initialData.actionConditions || prev.actionConditions,
|
|
||||||
actionFieldMappings: initialData.actionFieldMappings || prev.actionFieldMappings,
|
|
||||||
|
|
||||||
currentStep: initialData.fromConnection && initialData.toConnection ? 4 : 1, // 연결 정보가 있으면 4단계부터 시작
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("✅ 초기 데이터 로드 완료");
|
|
||||||
}
|
|
||||||
}, [initialData]);
|
|
||||||
|
|
||||||
// 🎯 액션 핸들러들
|
|
||||||
const actions: DataConnectionActions = {
|
|
||||||
// 연결 타입 설정
|
|
||||||
setConnectionType: useCallback((type: "data_save" | "external_call") => {
|
|
||||||
console.log("🔄 [DataConnectionDesigner] setConnectionType 호출됨:", type);
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
connectionType: type,
|
|
||||||
// 타입 변경 시 상태 초기화
|
|
||||||
currentStep: 1,
|
|
||||||
fromConnection: undefined,
|
|
||||||
toConnection: undefined,
|
|
||||||
fromTable: undefined,
|
|
||||||
toTable: undefined,
|
|
||||||
fieldMappings: [],
|
|
||||||
validationErrors: [],
|
|
||||||
}));
|
|
||||||
toast.success(`연결 타입이 ${type === "data_save" ? "데이터 저장" : "외부 호출"}로 변경되었습니다.`);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 🔧 관계 정보 설정
|
|
||||||
setRelationshipName: useCallback((name: string) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
relationshipName: name,
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
setDescription: useCallback((description: string) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
description: description,
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
setGroupsLogicalOperator: useCallback((operator: "AND" | "OR") => {
|
|
||||||
setState((prev) => ({ ...prev, groupsLogicalOperator: operator }));
|
|
||||||
console.log("🔄 그룹 간 논리 연산자 변경:", operator);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 단계 이동
|
|
||||||
goToStep: useCallback((step: 1 | 2 | 3 | 4) => {
|
|
||||||
setState((prev) => ({ ...prev, currentStep: step }));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 연결 선택
|
|
||||||
selectConnection: useCallback((type: "from" | "to", connection: Connection) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[type === "from" ? "fromConnection" : "toConnection"]: connection,
|
|
||||||
// 연결 변경 시 테이블과 매핑 초기화
|
|
||||||
[type === "from" ? "fromTable" : "toTable"]: undefined,
|
|
||||||
fieldMappings: [],
|
|
||||||
}));
|
|
||||||
toast.success(`${type === "from" ? "소스" : "대상"} 연결이 선택되었습니다: ${connection.name}`);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 테이블 선택
|
|
||||||
selectTable: useCallback((type: "from" | "to", table: TableInfo) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[type === "from" ? "fromTable" : "toTable"]: table,
|
|
||||||
// 테이블 변경 시 매핑과 컬럼 정보 초기화
|
|
||||||
fieldMappings: [],
|
|
||||||
fromColumns: type === "from" ? [] : prev.fromColumns,
|
|
||||||
toColumns: type === "to" ? [] : prev.toColumns,
|
|
||||||
}));
|
|
||||||
toast.success(
|
|
||||||
`${type === "from" ? "소스" : "대상"} 테이블이 선택되었습니다: ${table.displayName || table.tableName}`,
|
|
||||||
);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 컬럼 정보 로드 (중앙 관리)
|
|
||||||
loadColumns: useCallback(async () => {
|
|
||||||
if (!state.fromConnection || !state.toConnection || !state.fromTable || !state.toTable) {
|
|
||||||
console.log("❌ 컬럼 로드: 필수 정보 누락");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 이미 로드된 경우 스킵 (배열 길이로 확인)
|
|
||||||
if (state.fromColumns && state.toColumns && state.fromColumns.length > 0 && state.toColumns.length > 0) {
|
|
||||||
console.log("✅ 컬럼 정보 이미 로드됨, 스킵", {
|
|
||||||
fromColumns: state.fromColumns.length,
|
|
||||||
toColumns: state.toColumns.length,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("🔄 중앙 컬럼 로드 시작:", {
|
|
||||||
from: `${state.fromConnection.id}/${state.fromTable.tableName}`,
|
|
||||||
to: `${state.toConnection.id}/${state.toTable.tableName}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
isLoading: true,
|
|
||||||
fromColumns: [],
|
|
||||||
toColumns: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [fromCols, toCols] = await Promise.all([
|
|
||||||
getColumnsFromConnection(state.fromConnection.id, state.fromTable.tableName),
|
|
||||||
getColumnsFromConnection(state.toConnection.id, state.toTable.tableName),
|
|
||||||
]);
|
|
||||||
|
|
||||||
console.log("✅ 중앙 컬럼 로드 완료:", {
|
|
||||||
fromColumns: fromCols.length,
|
|
||||||
toColumns: toCols.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fromColumns: Array.isArray(fromCols) ? fromCols : [],
|
|
||||||
toColumns: Array.isArray(toCols) ? toCols : [],
|
|
||||||
isLoading: false,
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 중앙 컬럼 로드 실패:", error);
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
toast.error("컬럼 정보를 불러오는데 실패했습니다.");
|
|
||||||
}
|
|
||||||
}, [state.fromConnection, state.toConnection, state.fromTable, state.toTable, state.fromColumns, state.toColumns]),
|
|
||||||
|
|
||||||
// 필드 매핑 생성 (호환성용 - 실제로는 각 액션에서 직접 관리)
|
|
||||||
createMapping: useCallback((fromField: ColumnInfo, toField: ColumnInfo) => {
|
|
||||||
const newMapping: FieldMapping = {
|
|
||||||
id: `${fromField.columnName}_to_${toField.columnName}_${Date.now()}`,
|
|
||||||
fromField,
|
|
||||||
toField,
|
|
||||||
isValid: true,
|
|
||||||
validationMessage: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fieldMappings: [...prev.fieldMappings, newMapping],
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("🔗 전역 매핑 생성 (호환성):", {
|
|
||||||
newMapping,
|
|
||||||
fieldName: `${fromField.columnName} → ${toField.columnName}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success(`매핑이 생성되었습니다: ${fromField.columnName} → ${toField.columnName}`);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 필드 매핑 업데이트
|
|
||||||
updateMapping: useCallback((mappingId: string, updates: Partial<FieldMapping>) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fieldMappings: prev.fieldMappings.map((mapping) =>
|
|
||||||
mapping.id === mappingId ? { ...mapping, ...updates } : mapping,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 필드 매핑 삭제 (호환성용 - 실제로는 각 액션에서 직접 관리)
|
|
||||||
deleteMapping: useCallback((mappingId: string) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
fieldMappings: prev.fieldMappings.filter((mapping) => mapping.id !== mappingId),
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("🗑️ 전역 매핑 삭제 (호환성):", { mappingId });
|
|
||||||
toast.success("매핑이 삭제되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 매핑 검증
|
|
||||||
validateMappings: useCallback(async (): Promise<ValidationResult> => {
|
|
||||||
setState((prev) => ({ ...prev, isLoading: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: 실제 검증 로직 구현
|
|
||||||
const result: ValidationResult = {
|
|
||||||
isValid: true,
|
|
||||||
errors: [],
|
|
||||||
warnings: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
validationErrors: result.errors,
|
|
||||||
isLoading: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 제어 조건 관리 (전체 실행 조건)
|
|
||||||
addControlCondition: useCallback(() => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
controlConditions: [
|
|
||||||
...prev.controlConditions,
|
|
||||||
{
|
|
||||||
id: Date.now().toString(),
|
|
||||||
type: "condition",
|
|
||||||
field: "",
|
|
||||||
operator: "=",
|
|
||||||
value: "",
|
|
||||||
dataType: "string",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
updateControlCondition: useCallback((index: number, condition: any) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
controlConditions: prev.controlConditions.map((cond, i) => (i === index ? { ...cond, ...condition } : cond)),
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
deleteControlCondition: useCallback((index: number) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
controlConditions: prev.controlConditions.filter((_, i) => i !== index),
|
|
||||||
}));
|
|
||||||
toast.success("제어 조건이 삭제되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 외부호출 설정 업데이트
|
|
||||||
updateExternalCallConfig: useCallback((config: any) => {
|
|
||||||
console.log("🔄 외부호출 설정 업데이트:", config);
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
externalCallConfig: config,
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 액션 설정 관리
|
|
||||||
setActionType: useCallback((type: "insert" | "update" | "delete" | "upsert") => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionType: type,
|
|
||||||
// INSERT가 아닌 경우 조건 초기화
|
|
||||||
actionConditions: type === "insert" ? [] : prev.actionConditions,
|
|
||||||
}));
|
|
||||||
toast.success(`액션 타입이 ${type.toUpperCase()}로 변경되었습니다.`);
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
addActionCondition: useCallback(() => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionConditions: [
|
|
||||||
...prev.actionConditions,
|
|
||||||
{
|
|
||||||
id: Date.now().toString(),
|
|
||||||
type: "condition",
|
|
||||||
field: "",
|
|
||||||
operator: "=",
|
|
||||||
value: "",
|
|
||||||
dataType: "string",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
updateActionCondition: useCallback((index: number, condition: any) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionConditions: prev.actionConditions.map((cond, i) => (i === index ? { ...cond, ...condition } : cond)),
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 🔧 액션 조건 배열 전체 업데이트 (ActionConditionBuilder용)
|
|
||||||
setActionConditions: useCallback((conditions: any[]) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionConditions: conditions,
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
deleteActionCondition: useCallback((index: number) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionConditions: prev.actionConditions.filter((_, i) => i !== index),
|
|
||||||
}));
|
|
||||||
toast.success("조건이 삭제되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 🎯 액션 그룹 관리 (멀티 액션)
|
|
||||||
addActionGroup: useCallback(() => {
|
|
||||||
const newGroupId = `group_${Date.now()}`;
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: [
|
|
||||||
...prev.actionGroups,
|
|
||||||
{
|
|
||||||
id: newGroupId,
|
|
||||||
name: `액션 그룹 ${prev.actionGroups.length + 1}`,
|
|
||||||
logicalOperator: "AND" as const,
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
id: `action_${Date.now()}`,
|
|
||||||
name: "액션 1",
|
|
||||||
actionType: "insert" as const,
|
|
||||||
conditions: [],
|
|
||||||
fieldMappings: [],
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
toast.success("새 액션 그룹이 추가되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
updateActionGroup: useCallback((groupId: string, updates: Partial<ActionGroup>) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: prev.actionGroups.map((group) => (group.id === groupId ? { ...group, ...updates } : group)),
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
deleteActionGroup: useCallback((groupId: string) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: prev.actionGroups.filter((group) => group.id !== groupId),
|
|
||||||
}));
|
|
||||||
toast.success("액션 그룹이 삭제되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
addActionToGroup: useCallback((groupId: string) => {
|
|
||||||
const newActionId = `action_${Date.now()}`;
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: prev.actionGroups.map((group) =>
|
|
||||||
group.id === groupId
|
|
||||||
? {
|
|
||||||
...group,
|
|
||||||
actions: [
|
|
||||||
...group.actions,
|
|
||||||
{
|
|
||||||
id: newActionId,
|
|
||||||
name: `액션 ${group.actions.length + 1}`,
|
|
||||||
actionType: "insert" as const,
|
|
||||||
conditions: [],
|
|
||||||
fieldMappings: [],
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: group,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
toast.success("새 액션이 추가되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
updateActionInGroup: useCallback((groupId: string, actionId: string, updates: Partial<SingleAction>) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: prev.actionGroups.map((group) =>
|
|
||||||
group.id === groupId
|
|
||||||
? {
|
|
||||||
...group,
|
|
||||||
actions: group.actions.map((action) => (action.id === actionId ? { ...action, ...updates } : action)),
|
|
||||||
}
|
|
||||||
: group,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
deleteActionFromGroup: useCallback((groupId: string, actionId: string) => {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
actionGroups: prev.actionGroups.map((group) =>
|
|
||||||
group.id === groupId
|
|
||||||
? {
|
|
||||||
...group,
|
|
||||||
actions: group.actions.filter((action) => action.id !== actionId),
|
|
||||||
}
|
|
||||||
: group,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
toast.success("액션이 삭제되었습니다.");
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
// 매핑 저장 (직접 저장)
|
|
||||||
saveMappings: useCallback(async () => {
|
|
||||||
// 관계명과 설명이 없으면 저장할 수 없음
|
|
||||||
if (!state.relationshipName?.trim()) {
|
|
||||||
toast.error("관계 이름을 입력해주세요.");
|
|
||||||
actions.goToStep(1); // 첫 번째 단계로 이동
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 외부호출인 경우 API URL만 확인 (테이블 검증 제외)
|
|
||||||
if (state.connectionType === "external_call") {
|
|
||||||
if (!state.externalCallConfig?.restApiSettings?.apiUrl) {
|
|
||||||
toast.error("API URL을 입력해주세요.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 외부호출은 테이블 정보 검증 건너뛰기
|
|
||||||
}
|
|
||||||
|
|
||||||
// 중복 체크 (수정 모드가 아닌 경우에만)
|
|
||||||
if (!diagramId) {
|
|
||||||
try {
|
|
||||||
const duplicateCheck = await checkRelationshipNameDuplicate(state.relationshipName, diagramId);
|
|
||||||
if (duplicateCheck.isDuplicate) {
|
|
||||||
toast.error(`"${state.relationshipName}" 이름이 이미 사용 중입니다. 다른 이름을 사용해주세요.`);
|
|
||||||
actions.goToStep(1); // 첫 번째 단계로 이동
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("중복 체크 실패:", error);
|
|
||||||
toast.error("관계명 중복 체크 중 오류가 발생했습니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState((prev) => ({ ...prev, isLoading: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 실제 저장 로직 구현 - connectionType에 따라 필요한 설정만 포함
|
|
||||||
let saveData: any = {
|
|
||||||
relationshipName: state.relationshipName,
|
|
||||||
description: state.description,
|
|
||||||
connectionType: state.connectionType,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (state.connectionType === "external_call") {
|
|
||||||
// 외부호출 타입인 경우: 외부호출 설정만 포함
|
|
||||||
console.log("💾 외부호출 타입 저장 - 외부호출 설정만 포함");
|
|
||||||
saveData = {
|
|
||||||
...saveData,
|
|
||||||
// 외부호출 관련 설정만 포함
|
|
||||||
externalCallConfig: state.externalCallConfig,
|
|
||||||
actionType: "external_call",
|
|
||||||
// 데이터 저장 관련 설정은 제외 (null/빈 배열로 설정)
|
|
||||||
fromConnection: null,
|
|
||||||
toConnection: null,
|
|
||||||
fromTable: null,
|
|
||||||
toTable: null,
|
|
||||||
actionGroups: [],
|
|
||||||
controlConditions: [],
|
|
||||||
actionConditions: [],
|
|
||||||
fieldMappings: [],
|
|
||||||
};
|
|
||||||
} else if (state.connectionType === "data_save") {
|
|
||||||
// 데이터 저장 타입인 경우: 데이터 저장 설정만 포함
|
|
||||||
console.log("💾 데이터 저장 타입 저장 - 데이터 저장 설정만 포함");
|
|
||||||
saveData = {
|
|
||||||
...saveData,
|
|
||||||
// 데이터 저장 관련 설정만 포함
|
|
||||||
fromConnection: state.fromConnection,
|
|
||||||
toConnection: state.toConnection,
|
|
||||||
fromTable: state.fromTable,
|
|
||||||
toTable: state.toTable,
|
|
||||||
actionGroups: state.actionGroups,
|
|
||||||
groupsLogicalOperator: state.groupsLogicalOperator,
|
|
||||||
controlConditions: state.controlConditions,
|
|
||||||
// 기존 호환성을 위한 필드들 (첫 번째 액션 그룹의 첫 번째 액션에서 추출)
|
|
||||||
actionType: state.actionGroups[0]?.actions[0]?.actionType || state.actionType || "insert",
|
|
||||||
actionConditions: state.actionGroups[0]?.actions[0]?.conditions || state.actionConditions || [],
|
|
||||||
fieldMappings: state.actionGroups[0]?.actions[0]?.fieldMappings || state.fieldMappings || [],
|
|
||||||
// 외부호출 관련 설정은 제외 (null로 설정)
|
|
||||||
externalCallConfig: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("💾 직접 저장 시작:", { saveData, diagramId, isEdit: !!diagramId });
|
|
||||||
|
|
||||||
// 데이터 저장 타입인 경우 기존 외부호출 설정 정리
|
|
||||||
if (state.connectionType === "data_save" && diagramId) {
|
|
||||||
console.log("🧹 데이터 저장 타입으로 변경 - 기존 외부호출 설정 정리");
|
|
||||||
try {
|
|
||||||
const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig");
|
|
||||||
|
|
||||||
// 기존 외부호출 설정이 있는지 확인하고 삭제 또는 비활성화
|
|
||||||
const existingConfigs = await ExternalCallConfigAPI.getConfigs({
|
|
||||||
company_code: "*",
|
|
||||||
is_active: "Y",
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingConfig = existingConfigs.data?.find(
|
|
||||||
(config: any) => config.config_name === (state.relationshipName || "외부호출 설정"),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingConfig) {
|
|
||||||
console.log("🗑️ 기존 외부호출 설정 비활성화:", existingConfig.id);
|
|
||||||
// 설정을 비활성화 (삭제하지 않고 is_active를 'N'으로 변경)
|
|
||||||
await ExternalCallConfigAPI.updateConfig(existingConfig.id, {
|
|
||||||
...existingConfig,
|
|
||||||
is_active: "N",
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (cleanupError) {
|
|
||||||
console.warn("⚠️ 외부호출 설정 정리 실패 (무시하고 계속):", cleanupError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 외부호출인 경우에만 external-call-configs에 설정 저장
|
|
||||||
if (state.connectionType === "external_call" && state.externalCallConfig) {
|
|
||||||
try {
|
|
||||||
const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig");
|
|
||||||
|
|
||||||
const configData = {
|
|
||||||
config_name: state.relationshipName || "외부호출 설정",
|
|
||||||
call_type: "rest-api",
|
|
||||||
api_type: "generic",
|
|
||||||
config_data: state.externalCallConfig.restApiSettings,
|
|
||||||
description: state.description || "",
|
|
||||||
company_code: "*", // 기본값
|
|
||||||
};
|
|
||||||
|
|
||||||
let configResult;
|
|
||||||
|
|
||||||
if (diagramId) {
|
|
||||||
// 수정 모드: 기존 설정이 있는지 확인하고 업데이트 또는 생성
|
|
||||||
console.log("🔄 수정 모드 - 외부호출 설정 처리");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 먼저 기존 설정 조회 시도
|
|
||||||
const existingConfigs = await ExternalCallConfigAPI.getConfigs({
|
|
||||||
company_code: "*",
|
|
||||||
is_active: "Y",
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingConfig = existingConfigs.data?.find(
|
|
||||||
(config: any) => config.config_name === (state.relationshipName || "외부호출 설정"),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingConfig) {
|
|
||||||
// 기존 설정 업데이트
|
|
||||||
console.log("📝 기존 외부호출 설정 업데이트:", existingConfig.id);
|
|
||||||
configResult = await ExternalCallConfigAPI.updateConfig(existingConfig.id, configData);
|
|
||||||
} else {
|
|
||||||
// 기존 설정이 없으면 새로 생성
|
|
||||||
console.log("🆕 새 외부호출 설정 생성 (수정 모드)");
|
|
||||||
configResult = await ExternalCallConfigAPI.createConfig(configData);
|
|
||||||
}
|
|
||||||
} catch (updateError) {
|
|
||||||
// 중복 생성 오류인 경우 무시하고 계속 진행
|
|
||||||
if (updateError.message && updateError.message.includes("이미 존재합니다")) {
|
|
||||||
console.log("⚠️ 외부호출 설정이 이미 존재함 - 기존 설정 사용");
|
|
||||||
configResult = { success: true, message: "기존 외부호출 설정 사용" };
|
|
||||||
} else {
|
|
||||||
console.warn("⚠️ 외부호출 설정 처리 실패:", updateError);
|
|
||||||
throw updateError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 신규 생성 모드
|
|
||||||
console.log("🆕 신규 생성 모드 - 외부호출 설정 생성");
|
|
||||||
try {
|
|
||||||
configResult = await ExternalCallConfigAPI.createConfig(configData);
|
|
||||||
} catch (createError) {
|
|
||||||
// 중복 생성 오류인 경우 무시하고 계속 진행
|
|
||||||
if (createError.message && createError.message.includes("이미 존재합니다")) {
|
|
||||||
console.log("⚠️ 외부호출 설정이 이미 존재함 - 기존 설정 사용");
|
|
||||||
configResult = { success: true, message: "기존 외부호출 설정 사용" };
|
|
||||||
} else {
|
|
||||||
throw createError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!configResult.success) {
|
|
||||||
throw new Error(configResult.error || "외부호출 설정 저장 실패");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("✅ 외부호출 설정 저장 완료:", configResult.data);
|
|
||||||
} catch (configError) {
|
|
||||||
console.error("❌ 외부호출 설정 저장 실패:", configError);
|
|
||||||
// 외부호출 설정 저장 실패해도 관계는 저장하도록 함
|
|
||||||
toast.error("외부호출 설정 저장에 실패했지만 관계는 저장되었습니다.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 백엔드 API 호출 (수정 모드인 경우 diagramId 전달)
|
|
||||||
const result = await saveDataflowRelationship(saveData, diagramId);
|
|
||||||
|
|
||||||
console.log("✅ 저장 완료:", result);
|
|
||||||
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
toast.success(`"${state.relationshipName}" 관계가 성공적으로 저장되었습니다.`);
|
|
||||||
|
|
||||||
// 저장 후 닫기
|
|
||||||
if (onClose) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("❌ 저장 실패:", error);
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
|
||||||
}
|
|
||||||
}, [state, diagramId, onClose]),
|
|
||||||
|
|
||||||
// 테스트 실행
|
|
||||||
testExecution: useCallback(async (): Promise<TestResult> => {
|
|
||||||
setState((prev) => ({ ...prev, isLoading: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: 실제 테스트 로직 구현
|
|
||||||
const result: TestResult = {
|
|
||||||
success: true,
|
|
||||||
message: "테스트가 성공적으로 완료되었습니다.",
|
|
||||||
affectedRows: 10,
|
|
||||||
executionTime: 250,
|
|
||||||
};
|
|
||||||
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
toast.success(result.message);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
setState((prev) => ({ ...prev, isLoading: false }));
|
|
||||||
toast.error("테스트 실행 중 오류가 발생했습니다.");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, []),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-gradient-to-br from-slate-50 to-gray-100">
|
<div className="h-screen bg-gradient-to-br from-slate-50 to-gray-100">
|
||||||
|
|
@ -746,18 +58,52 @@ const initialState: DataConnectionState = {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 메인 컨텐츠 - 좌우 분할 레이아웃 */}
|
<div className="w-[70%] bg-gray-50 flex flex-col">
|
||||||
<div className="flex h-[calc(100vh-200px)] min-h-[700px] overflow-hidden">
|
<StepProgress
|
||||||
{/* 좌측 패널 (30%) - 항상 표시 */}
|
currentStep={state.currentStep}
|
||||||
<div className="flex w-[20%] flex-col border-r bg-white">
|
onStepChange={(step) => setState(prev => ({ ...prev, currentStep: step }))}
|
||||||
<LeftPanel state={state} actions={actions} />
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1 p-6">
|
||||||
|
{state.currentStep === 1 && (
|
||||||
|
<ConnectionStep
|
||||||
|
fromConnection={state.fromConnection}
|
||||||
|
toConnection={state.toConnection}
|
||||||
|
onFromConnectionChange={(conn) => setState(prev => ({ ...prev, fromConnection: conn }))}
|
||||||
|
onToConnectionChange={(conn) => setState(prev => ({ ...prev, toConnection: conn }))}
|
||||||
|
onNext={() => setState(prev => ({ ...prev, currentStep: 2 }))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state.currentStep === 2 && (
|
||||||
|
<TableStep
|
||||||
|
fromConnection={state.fromConnection}
|
||||||
|
toConnection={state.toConnection}
|
||||||
|
fromTable={state.fromTable}
|
||||||
|
toTable={state.toTable}
|
||||||
|
onFromTableChange={(table) => setState(prev => ({ ...prev, fromTable: table }))}
|
||||||
|
onToTableChange={(table) => setState(prev => ({ ...prev, toTable: table }))}
|
||||||
|
onNext={() => setState(prev => ({ ...prev, currentStep: 3 }))}
|
||||||
|
onBack={() => setState(prev => ({ ...prev, currentStep: 1 }))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state.currentStep === 3 && (
|
||||||
|
<FieldMappingStep
|
||||||
|
fromTable={state.fromTable}
|
||||||
|
toTable={state.toTable}
|
||||||
|
fieldMappings={state.fieldMappings}
|
||||||
|
onMappingsChange={(mappings) => setState(prev => ({ ...prev, fieldMappings: mappings }))}
|
||||||
|
onBack={() => setState(prev => ({ ...prev, currentStep: 2 }))}
|
||||||
|
onSave={() => {
|
||||||
|
// 저장 로직
|
||||||
|
console.log("저장:", state);
|
||||||
|
alert("데이터 연결 설정이 저장되었습니다!");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 우측 패널 (80%) */}
|
|
||||||
<div className="flex w-[80%] flex-col bg-gray-50">
|
|
||||||
<RightPanel key={state.connectionType} state={state} actions={actions} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue