플로우 외부연결 중간커밋
This commit is contained in:
@@ -16,6 +16,8 @@ import { getTableColumns } from "@/lib/api/tableManagement";
|
||||
interface FlowConditionBuilderProps {
|
||||
flowId: number;
|
||||
tableName?: string; // 조회할 테이블명
|
||||
dbSourceType?: "internal" | "external"; // DB 소스 타입
|
||||
dbConnectionId?: number; // 외부 DB 연결 ID
|
||||
condition?: FlowConditionGroup;
|
||||
onChange: (condition: FlowConditionGroup | undefined) => void;
|
||||
}
|
||||
@@ -35,7 +37,14 @@ const OPERATORS: { value: ConditionOperator; label: string }[] = [
|
||||
{ value: "is_not_null", label: "NOT NULL" },
|
||||
];
|
||||
|
||||
export function FlowConditionBuilder({ flowId, tableName, condition, onChange }: FlowConditionBuilderProps) {
|
||||
export function FlowConditionBuilder({
|
||||
flowId,
|
||||
tableName,
|
||||
dbSourceType = "internal",
|
||||
dbConnectionId,
|
||||
condition,
|
||||
onChange,
|
||||
}: FlowConditionBuilderProps) {
|
||||
const [columns, setColumns] = useState<any[]>([]);
|
||||
const [loadingColumns, setLoadingColumns] = useState(false);
|
||||
const [conditionType, setConditionType] = useState<"AND" | "OR">(condition?.type || "AND");
|
||||
@@ -52,7 +61,7 @@ export function FlowConditionBuilder({ flowId, tableName, condition, onChange }:
|
||||
}
|
||||
}, [condition]);
|
||||
|
||||
// 테이블 컬럼 로드
|
||||
// 테이블 컬럼 로드 - 내부/외부 DB 모두 지원
|
||||
useEffect(() => {
|
||||
if (!tableName) {
|
||||
setColumns([]);
|
||||
@@ -62,17 +71,69 @@ export function FlowConditionBuilder({ flowId, tableName, condition, onChange }:
|
||||
const loadColumns = async () => {
|
||||
try {
|
||||
setLoadingColumns(true);
|
||||
console.log("🔍 Loading columns for table:", tableName);
|
||||
const response = await getTableColumns(tableName);
|
||||
console.log("📦 Column API response:", response);
|
||||
console.log("🔍 [FlowConditionBuilder] Loading columns:", {
|
||||
tableName,
|
||||
dbSourceType,
|
||||
dbConnectionId,
|
||||
});
|
||||
|
||||
if (response.success && response.data?.columns) {
|
||||
const columnArray = Array.isArray(response.data.columns) ? response.data.columns : [];
|
||||
console.log("✅ Setting columns:", columnArray.length, "items");
|
||||
setColumns(columnArray);
|
||||
// 외부 DB인 경우
|
||||
if (dbSourceType === "external" && dbConnectionId) {
|
||||
const token = localStorage.getItem("authToken");
|
||||
if (!token) {
|
||||
console.warn("토큰이 없습니다. 외부 DB 컬럼 목록을 조회할 수 없습니다.");
|
||||
setColumns([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`/api/multi-connection/connections/${dbConnectionId}/tables/${tableName}/columns`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
).catch((err) => {
|
||||
console.warn("외부 DB 컬럼 fetch 실패:", err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
const result = await response.json();
|
||||
console.log("✅ [FlowConditionBuilder] External columns response:", result);
|
||||
|
||||
if (result.success && result.data) {
|
||||
const columnList = Array.isArray(result.data)
|
||||
? result.data.map((col: any) => ({
|
||||
column_name: col.column_name || col.columnName || col.name,
|
||||
data_type: col.data_type || col.dataType || col.type,
|
||||
}))
|
||||
: [];
|
||||
console.log("✅ Setting external columns:", columnList.length, "items");
|
||||
setColumns(columnList);
|
||||
} else {
|
||||
console.warn("❌ No data in external columns response");
|
||||
setColumns([]);
|
||||
}
|
||||
} else {
|
||||
console.warn(`외부 DB 컬럼 조회 실패: ${response?.status}`);
|
||||
setColumns([]);
|
||||
}
|
||||
} else {
|
||||
console.error("❌ Failed to load columns:", response.message);
|
||||
setColumns([]);
|
||||
// 내부 DB인 경우 (기존 로직)
|
||||
const response = await getTableColumns(tableName);
|
||||
console.log("📦 [FlowConditionBuilder] Internal columns response:", response);
|
||||
|
||||
if (response.success && response.data?.columns) {
|
||||
const columnArray = Array.isArray(response.data.columns) ? response.data.columns : [];
|
||||
console.log("✅ Setting internal columns:", columnArray.length, "items");
|
||||
setColumns(columnArray);
|
||||
} else {
|
||||
console.error("❌ Failed to load internal columns:", response.message);
|
||||
setColumns([]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Exception loading columns:", error);
|
||||
@@ -83,7 +144,7 @@ export function FlowConditionBuilder({ flowId, tableName, condition, onChange }:
|
||||
};
|
||||
|
||||
loadColumns();
|
||||
}, [tableName]);
|
||||
}, [tableName, dbSourceType, dbConnectionId]);
|
||||
|
||||
// 조건 변경 시 부모에 전달
|
||||
useEffect(() => {
|
||||
|
||||
@@ -31,16 +31,35 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
interface FlowStepPanelProps {
|
||||
step: FlowStep;
|
||||
flowId: number;
|
||||
flowTableName?: string; // 플로우 정의에서 선택한 테이블명
|
||||
flowDbSourceType?: "internal" | "external"; // 플로우의 DB 소스 타입
|
||||
flowDbConnectionId?: number; // 플로우의 외부 DB 연결 ID
|
||||
onClose: () => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanelProps) {
|
||||
export function FlowStepPanel({
|
||||
step,
|
||||
flowId,
|
||||
flowTableName,
|
||||
flowDbSourceType = "internal",
|
||||
flowDbConnectionId,
|
||||
onClose,
|
||||
onUpdate,
|
||||
}: FlowStepPanelProps) {
|
||||
const { toast } = useToast();
|
||||
|
||||
console.log("🎯 FlowStepPanel Props:", {
|
||||
stepTableName: step.tableName,
|
||||
flowTableName,
|
||||
flowDbSourceType,
|
||||
flowDbConnectionId,
|
||||
final: step.tableName || flowTableName || "",
|
||||
});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
stepName: step.stepName,
|
||||
tableName: step.tableName || "",
|
||||
tableName: step.tableName || flowTableName || "", // 플로우 테이블명 우선 사용 (신규 방식)
|
||||
conditionJson: step.conditionJson,
|
||||
// 하이브리드 모드 필드
|
||||
moveType: step.moveType || "status",
|
||||
@@ -215,11 +234,12 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
stepName: step.stepName,
|
||||
statusColumn: step.statusColumn,
|
||||
statusValue: step.statusValue,
|
||||
flowTableName, // 플로우 정의의 테이블명
|
||||
});
|
||||
|
||||
const newFormData = {
|
||||
stepName: step.stepName,
|
||||
tableName: step.tableName || "",
|
||||
tableName: step.tableName || flowTableName || "", // 플로우 테이블명 우선 사용
|
||||
conditionJson: step.conditionJson,
|
||||
// 하이브리드 모드 필드
|
||||
moveType: step.moveType || "status",
|
||||
@@ -234,9 +254,9 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
|
||||
console.log("✅ Setting formData:", newFormData);
|
||||
setFormData(newFormData);
|
||||
}, [step.id]); // step 전체가 아닌 step.id만 의존성으로 설정
|
||||
}, [step.id, flowTableName]); // flowTableName도 의존성 추가
|
||||
|
||||
// 테이블 선택 시 컬럼 로드
|
||||
// 테이블 선택 시 컬럼 로드 - 내부/외부 DB 모두 지원
|
||||
useEffect(() => {
|
||||
const loadColumns = async () => {
|
||||
if (!formData.tableName) {
|
||||
@@ -246,16 +266,70 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
|
||||
try {
|
||||
setLoadingColumns(true);
|
||||
console.log("🔍 Loading columns for status column selector:", formData.tableName);
|
||||
const response = await getTableColumns(formData.tableName);
|
||||
console.log("📦 Columns response:", response);
|
||||
console.log("🔍 Loading columns for status column selector:", {
|
||||
tableName: formData.tableName,
|
||||
flowDbSourceType,
|
||||
flowDbConnectionId,
|
||||
});
|
||||
|
||||
if (response.success && response.data && response.data.columns) {
|
||||
console.log("✅ Setting columns:", response.data.columns);
|
||||
setColumns(response.data.columns);
|
||||
// 외부 DB인 경우
|
||||
if (flowDbSourceType === "external" && flowDbConnectionId) {
|
||||
const token = localStorage.getItem("authToken");
|
||||
if (!token) {
|
||||
console.warn("토큰이 없습니다. 외부 DB 컬럼 목록을 조회할 수 없습니다.");
|
||||
setColumns([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 외부 DB 컬럼 조회 API
|
||||
const response = await fetch(
|
||||
`/api/multi-connection/connections/${flowDbConnectionId}/tables/${formData.tableName}/columns`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
).catch((err) => {
|
||||
console.warn("외부 DB 컬럼 목록 fetch 실패:", err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
const result = await response.json();
|
||||
console.log("✅ External columns API response:", result);
|
||||
|
||||
if (result.success && result.data) {
|
||||
// 컬럼 데이터 형식 통일
|
||||
const columnList = Array.isArray(result.data)
|
||||
? result.data.map((col: any) => ({
|
||||
column_name: col.column_name || col.columnName || col.name,
|
||||
data_type: col.data_type || col.dataType || col.type,
|
||||
}))
|
||||
: [];
|
||||
console.log("✅ Setting external columns:", columnList);
|
||||
setColumns(columnList);
|
||||
} else {
|
||||
console.warn("❌ No data in external columns response");
|
||||
setColumns([]);
|
||||
}
|
||||
} else {
|
||||
console.warn(`외부 DB 컬럼 목록 조회 실패: ${response?.status || "네트워크 오류"}`);
|
||||
setColumns([]);
|
||||
}
|
||||
} else {
|
||||
console.log("❌ No columns in response");
|
||||
setColumns([]);
|
||||
// 내부 DB인 경우 (기존 로직)
|
||||
const response = await getTableColumns(formData.tableName);
|
||||
console.log("📦 Internal columns response:", response);
|
||||
|
||||
if (response.success && response.data && response.data.columns) {
|
||||
console.log("✅ Setting internal columns:", response.data.columns);
|
||||
setColumns(response.data.columns);
|
||||
} else {
|
||||
console.log("❌ No columns in response");
|
||||
setColumns([]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load columns:", error);
|
||||
@@ -266,7 +340,7 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
};
|
||||
|
||||
loadColumns();
|
||||
}, [formData.tableName]);
|
||||
}, [formData.tableName, flowDbSourceType, flowDbConnectionId]);
|
||||
|
||||
// formData의 최신 값을 항상 참조하기 위한 ref
|
||||
const formDataRef = useRef(formData);
|
||||
@@ -280,6 +354,27 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
const handleSave = useCallback(async () => {
|
||||
const currentFormData = formDataRef.current;
|
||||
console.log("🚀 handleSave called, formData:", JSON.stringify(currentFormData, null, 2));
|
||||
|
||||
// 상태 변경 방식일 때 필수 필드 검증
|
||||
if (currentFormData.moveType === "status") {
|
||||
if (!currentFormData.statusColumn) {
|
||||
toast({
|
||||
title: "입력 오류",
|
||||
description: "상태 변경 방식을 사용하려면 '상태 컬럼명'을 반드시 지정해야 합니다.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!currentFormData.statusValue) {
|
||||
toast({
|
||||
title: "입력 오류",
|
||||
description: "상태 변경 방식을 사용하려면 '이 단계의 상태값'을 반드시 지정해야 합니다.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await updateFlowStep(step.id, currentFormData);
|
||||
console.log("📡 API response:", response);
|
||||
@@ -368,8 +463,9 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
<Input value={step.stepOrder} disabled />
|
||||
</div>
|
||||
|
||||
{/* ===== 구버전: 단계별 테이블 선택 방식 (주석처리) ===== */}
|
||||
{/* DB 소스 선택 */}
|
||||
<div>
|
||||
{/* <div>
|
||||
<Label>데이터베이스 소스</Label>
|
||||
<Select
|
||||
value={selectedDbSource.toString()}
|
||||
@@ -393,10 +489,10 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mt-1 text-xs text-gray-500">조회할 데이터베이스를 선택합니다</p>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* 테이블 선택 */}
|
||||
<div>
|
||||
{/* <div>
|
||||
<Label>조회할 테이블</Label>
|
||||
<Popover open={openTableCombobox} onOpenChange={setOpenTableCombobox}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -478,7 +574,16 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
? "이 단계에서 조건을 적용할 테이블을 선택합니다"
|
||||
: "외부 데이터베이스의 테이블을 선택합니다"}
|
||||
</p>
|
||||
</div> */}
|
||||
{/* ===== 구버전 끝 ===== */}
|
||||
|
||||
{/* ===== 신버전: 플로우에서 선택한 테이블 표시만 ===== */}
|
||||
<div>
|
||||
<Label>연결된 테이블</Label>
|
||||
<Input value={formData.tableName || "테이블이 지정되지 않았습니다"} disabled className="bg-gray-50" />
|
||||
<p className="mt-1 text-xs text-gray-500">플로우 생성 시 선택한 테이블입니다 (수정 불가)</p>
|
||||
</div>
|
||||
{/* ===== 신버전 끝 ===== */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -495,6 +600,8 @@ export function FlowStepPanel({ step, flowId, onClose, onUpdate }: FlowStepPanel
|
||||
<FlowConditionBuilder
|
||||
flowId={flowId}
|
||||
tableName={formData.tableName}
|
||||
dbSourceType={flowDbSourceType}
|
||||
dbConnectionId={flowDbConnectionId}
|
||||
condition={formData.conditionJson}
|
||||
onChange={(condition) => setFormData({ ...formData, conditionJson: condition })}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user