플로우 외부연결 중간커밋

This commit is contained in:
kjs
2025-10-21 13:19:18 +09:00
parent 967f9a9f5b
commit 0d96ea566b
12 changed files with 1667 additions and 100 deletions

View File

@@ -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(() => {

View File

@@ -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 })}
/>