제어관리 외부커넥션 설정기능
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ArrowLeft, Settings, CheckCircle } from "lucide-react";
|
||||
|
||||
// 타입 import
|
||||
import { DataConnectionState, DataConnectionActions } from "../types/redesigned";
|
||||
import { ColumnInfo } from "@/lib/types/multiConnection";
|
||||
import { getColumnsFromConnection } from "@/lib/api/multiConnection";
|
||||
|
||||
// 컴포넌트 import
|
||||
import ActionConditionBuilder from "./ActionConfig/ActionConditionBuilder";
|
||||
|
||||
interface ActionConfigStepProps {
|
||||
state: DataConnectionState;
|
||||
actions: DataConnectionActions;
|
||||
onBack: () => void;
|
||||
onComplete: () => void;
|
||||
onSave?: () => void; // UPDATE/DELETE인 경우 저장 버튼
|
||||
showSaveButton?: boolean; // 저장 버튼 표시 여부
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 4단계: 액션 설정
|
||||
* - 액션 타입 선택 (INSERT/UPDATE/DELETE/UPSERT)
|
||||
* - 실행 조건 설정
|
||||
* - 액션별 상세 설정
|
||||
*/
|
||||
const ActionConfigStep: React.FC<ActionConfigStepProps> = ({
|
||||
state,
|
||||
actions,
|
||||
onBack,
|
||||
onComplete,
|
||||
onSave,
|
||||
showSaveButton = false,
|
||||
}) => {
|
||||
const { actionType, actionConditions, fromTable, toTable, fromConnection, toConnection } = state;
|
||||
|
||||
const [fromColumns, setFromColumns] = useState<ColumnInfo[]>([]);
|
||||
const [toColumns, setToColumns] = useState<ColumnInfo[]>([]);
|
||||
const [fieldMappings, setFieldMappings] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const actionTypes = [
|
||||
{ value: "insert", label: "INSERT", description: "새 데이터 삽입" },
|
||||
{ value: "update", label: "UPDATE", description: "기존 데이터 수정" },
|
||||
{ value: "delete", label: "DELETE", description: "데이터 삭제" },
|
||||
{ value: "upsert", label: "UPSERT", description: "있으면 수정, 없으면 삽입" },
|
||||
];
|
||||
|
||||
// 컬럼 정보 로드
|
||||
useEffect(() => {
|
||||
const loadColumns = async () => {
|
||||
if (!fromConnection || !toConnection || !fromTable || !toTable) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [fromCols, toCols] = await Promise.all([
|
||||
getColumnsFromConnection(fromConnection.id, fromTable.tableName),
|
||||
getColumnsFromConnection(toConnection.id, toTable.tableName),
|
||||
]);
|
||||
|
||||
setFromColumns(fromCols);
|
||||
setToColumns(toCols);
|
||||
} catch (error) {
|
||||
console.error("컬럼 정보 로드 실패:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadColumns();
|
||||
}, [fromConnection, toConnection, fromTable, toTable]);
|
||||
|
||||
const canComplete =
|
||||
actionType &&
|
||||
(actionType === "insert" || (actionConditions.length > 0 && (actionType === "delete" || fieldMappings.length > 0)));
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
4단계: 액션 설정
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="max-h-[calc(100vh-400px)] min-h-[400px] space-y-6 overflow-y-auto">
|
||||
{/* 액션 타입 선택 */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-lg font-semibold">액션 타입</h3>
|
||||
<Select value={actionType} onValueChange={actions.setActionType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="액션 타입을 선택하세요" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{actionTypes.map((type) => (
|
||||
<SelectItem key={type.value} value={type.value}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div>
|
||||
<span className="font-medium">{type.label}</span>
|
||||
<p className="text-muted-foreground text-xs">{type.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{actionType && (
|
||||
<div className="bg-primary/5 border-primary/20 rounded-lg border p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-primary">
|
||||
{actionTypes.find((t) => t.value === actionType)?.label}
|
||||
</Badge>
|
||||
<span className="text-sm">{actionTypes.find((t) => t.value === actionType)?.description}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 상세 조건 설정 */}
|
||||
{actionType && !isLoading && fromColumns.length > 0 && toColumns.length > 0 && (
|
||||
<ActionConditionBuilder
|
||||
actionType={actionType}
|
||||
fromColumns={fromColumns}
|
||||
toColumns={toColumns}
|
||||
conditions={actionConditions}
|
||||
fieldMappings={fieldMappings}
|
||||
onConditionsChange={(conditions) => {
|
||||
// 액션 조건 배열 전체 업데이트
|
||||
actions.setActionConditions(conditions);
|
||||
}}
|
||||
onFieldMappingsChange={setFieldMappings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 로딩 상태 */}
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-muted-foreground">컬럼 정보를 불러오는 중...</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* INSERT 액션 안내 */}
|
||||
{actionType === "insert" && (
|
||||
<div className="rounded-lg border border-green-200 bg-green-50 p-4">
|
||||
<h4 className="mb-2 text-sm font-medium text-green-800">INSERT 액션</h4>
|
||||
<p className="text-sm text-green-700">
|
||||
INSERT 액션은 별도의 실행 조건이 필요하지 않습니다. 매핑된 모든 데이터가 새로운 레코드로 삽입됩니다.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 액션 요약 */}
|
||||
{actionType && (
|
||||
<div className="bg-muted/50 rounded-lg p-4">
|
||||
<h4 className="mb-3 text-sm font-medium">설정 요약</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>액션 타입:</span>
|
||||
<Badge variant="outline">{actionType.toUpperCase()}</Badge>
|
||||
</div>
|
||||
{actionType !== "insert" && (
|
||||
<>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>실행 조건:</span>
|
||||
<span className="text-muted-foreground">
|
||||
{actionConditions.length > 0 ? `${actionConditions.length}개 조건` : "조건 없음"}
|
||||
</span>
|
||||
</div>
|
||||
{actionType !== "delete" && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>필드 매핑:</span>
|
||||
<span className="text-muted-foreground">
|
||||
{fieldMappings.length > 0 ? `${fieldMappings.length}개 필드` : "필드 없음"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 하단 네비게이션 */}
|
||||
<div className="border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="outline" onClick={onBack} className="flex items-center gap-2">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
이전: 제어 조건
|
||||
</Button>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{showSaveButton && onSave && (
|
||||
<Button onClick={onSave} disabled={!canComplete} className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
저장
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!showSaveButton && (
|
||||
<Button onClick={onComplete} disabled={!canComplete} className="flex items-center gap-2">
|
||||
다음: 컬럼 매핑
|
||||
<ArrowLeft className="h-4 w-4 rotate-180" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!canComplete && (
|
||||
<p className="text-muted-foreground mt-2 text-center text-sm">
|
||||
{!actionType ? "액션 타입을 선택해주세요" : "실행 조건을 추가해주세요"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionConfigStep;
|
||||
Reference in New Issue
Block a user