ERP-node/frontend/components/dataflow/connection/redesigned/RightPanel/ControlConditionStep.tsx

473 lines
20 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { ArrowLeft, CheckCircle, AlertCircle, Settings, Plus, Trash2 } from "lucide-react";
// 타입 import
import { DataConnectionState, DataConnectionActions } from "../types/redesigned";
import { ColumnInfo } from "@/lib/types/multiConnection";
2025-09-26 13:52:32 +09:00
// 컬럼 로드는 중앙에서 관리
import { getCodesForColumn, CodeItem } from "@/lib/api/codeManagement";
// 컴포넌트 import
interface ControlConditionStepProps {
state: DataConnectionState;
actions: DataConnectionActions;
onBack: () => void;
onNext: () => void;
}
/**
* 🎯 4단계: 제어
* -
* - INSERT/UPDATE/DELETE
*/
const ControlConditionStep: React.FC<ControlConditionStepProps> = ({ state, actions, onBack, onNext }) => {
2025-09-26 13:52:32 +09:00
const {
controlConditions,
fromTable,
toTable,
fromConnection,
toConnection,
fromColumns = [],
toColumns = [],
isLoading = false,
} = state;
const [availableCodes, setAvailableCodes] = useState<Record<string, CodeItem[]>>({});
2025-09-26 13:52:32 +09:00
// 컴포넌트 마운트 시 컬럼 로드
useEffect(() => {
2025-09-26 13:52:32 +09:00
if (
fromConnection &&
toConnection &&
fromTable &&
toTable &&
fromColumns.length === 0 &&
toColumns.length === 0 &&
!isLoading
) {
console.log("🔄 ControlConditionStep: 컬럼 로드 시작");
actions.loadColumns();
}
}, [
fromConnection?.id,
toConnection?.id,
fromTable?.tableName,
toTable?.tableName,
fromColumns.length,
toColumns.length,
isLoading,
]);
// 코드 타입 컬럼의 코드 로드
useEffect(() => {
const loadCodes = async () => {
const allColumns = [...fromColumns, ...toColumns];
2025-09-26 13:52:32 +09:00
const codeColumns = allColumns.filter((col) => {
// 메인 DB(connectionId === 0 또는 undefined)인 경우: column_labels의 input_type이 'code'인 경우만
if (col.connectionId === 0 || col.connectionId === undefined) {
return col.inputType === "code";
}
// 외부 DB인 경우: 코드 타입 없음
return false;
});
if (codeColumns.length === 0) return;
2025-09-26 13:52:32 +09:00
console.log(
"🔍 모든 컬럼 정보:",
allColumns.map((col) => ({
columnName: col.columnName,
connectionId: col.connectionId,
inputType: col.inputType,
webType: col.webType,
})),
);
console.log("🔍 코드 타입 컬럼들:", codeColumns);
const codePromises = codeColumns.map(async (col) => {
try {
const codes = await getCodesForColumn(col.columnName, col.webType, col.codeCategory);
return { columnName: col.columnName, codes };
} catch (error) {
console.error(`코드 로딩 실패 (${col.columnName}):`, error);
return { columnName: col.columnName, codes: [] };
}
});
const results = await Promise.all(codePromises);
const codeMap: Record<string, CodeItem[]> = {};
results.forEach(({ columnName, codes }) => {
codeMap[columnName] = codes;
});
console.log("📋 로딩된 코드들:", codeMap);
setAvailableCodes(codeMap);
};
if (fromColumns.length > 0 || toColumns.length > 0) {
loadCodes();
}
}, [fromColumns, toColumns]);
// 완료 가능 여부 확인
const canProceed =
controlConditions.length === 0 ||
controlConditions.some(
(condition) =>
condition.field &&
condition.operator &&
(condition.value !== "" || ["IS NULL", "IS NOT NULL"].includes(condition.operator)),
);
const isCompleted = canProceed;
return (
<>
<CardHeader>
<CardTitle className="flex items-center gap-2">
{isCompleted ? (
<CheckCircle className="h-5 w-5 text-green-600" />
) : (
<AlertCircle className="h-5 w-5 text-orange-500" />
)}
4단계: 제어
</CardTitle>
<p className="text-muted-foreground text-sm">
. .
</p>
</CardHeader>
<CardContent className="flex h-full flex-col overflow-hidden p-0">
<div className="min-h-0 flex-1 space-y-6 overflow-y-auto p-4">
{/* 제어 실행 조건 안내 */}
<div className="rounded-lg border border-primary/20 bg-accent p-4">
<h4 className="mb-2 text-sm font-medium text-blue-800"> ?</h4>
<div className="space-y-1 text-sm text-blue-700">
<p>
<strong> </strong>
</p>
<p> : "상태가 '활성'이고 유형이 'A'인 경우에만 데이터 동기화 실행"</p>
<p> </p>
</div>
</div>
{/* 간단한 조건 추가 UI */}
{!isLoading && (fromColumns.length > 0 || toColumns.length > 0 || controlConditions.length > 0) && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium"> (WHERE)</h4>
<Button
variant="outline"
size="sm"
onClick={() => {
console.log("🔄 조건 추가 클릭");
actions.addControlCondition();
}}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
</Button>
</div>
{controlConditions.length === 0 ? (
<div className="rounded-lg border-2 border-dashed p-6 text-center">
<Settings className="text-muted-foreground mx-auto mb-2 h-6 w-6" />
<p className="text-muted-foreground text-sm"> </p>
<p className="text-muted-foreground mt-1 text-xs">"조건 추가" </p>
</div>
) : (
<div className="space-y-3">
{controlConditions.map((condition, index) => (
<div key={`control-condition-${index}`} className="rounded-lg border p-3">
<div className="flex items-center gap-3">
{/* 논리 연산자 */}
{index > 0 && (
<Select
value={condition.logicalOperator || "AND"}
onValueChange={(value) =>
actions.updateControlCondition(index, {
...condition,
logicalOperator: value as "AND" | "OR",
})
}
>
<SelectTrigger className="w-16">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AND">AND</SelectItem>
<SelectItem value="OR">OR</SelectItem>
</SelectContent>
</Select>
)}
{/* 필드 선택 */}
<Select
value={condition.field || ""}
onValueChange={(value) => {
if (value !== "__placeholder__") {
actions.updateControlCondition(index, {
...condition,
field: value,
2025-09-26 13:52:32 +09:00
value: "", // 필드 변경 시 값 초기화
});
}
}}
>
<SelectTrigger className="w-48">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__placeholder__" disabled>
</SelectItem>
{[...fromColumns, ...toColumns]
.filter(
(col, index, array) =>
col.columnName && // 빈 문자열 제외
array.findIndex((c) => c.columnName === col.columnName) === index,
)
.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.displayName || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
{/* 연산자 선택 */}
<Select
value={condition.operator || "="}
onValueChange={(value) =>
actions.updateControlCondition(index, {
...condition,
operator: value as any,
})
}
>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="=">=</SelectItem>
<SelectItem value="!=">!=</SelectItem>
<SelectItem value=">">{">"}</SelectItem>
<SelectItem value="<">{"<"}</SelectItem>
<SelectItem value=">=">{">="}</SelectItem>
<SelectItem value="<=">{`<=`}</SelectItem>
<SelectItem value="LIKE">LIKE</SelectItem>
<SelectItem value="IS NULL">IS NULL</SelectItem>
<SelectItem value="IS NOT NULL">IS NOT NULL</SelectItem>
</SelectContent>
</Select>
{/* 값 입력 */}
{!["IS NULL", "IS NOT NULL"].includes(condition.operator || "") &&
(() => {
// 선택된 필드가 코드 타입인지 확인
const selectedField = [...fromColumns, ...toColumns].find(
(col) => col.columnName === condition.field,
);
const isCodeField =
selectedField &&
2025-09-26 13:52:32 +09:00
(selectedField.connectionId === 0 || selectedField.connectionId === undefined) && // 임시: undefined도 메인 DB로 간주
selectedField.inputType === "code";
const fieldCodes = condition.field ? availableCodes[condition.field] : [];
// 디버깅 정보 출력
console.log("🔍 값 입력 필드 디버깅:", {
conditionField: condition.field,
selectedField: selectedField,
2025-09-26 13:52:32 +09:00
selectedFieldKeys: selectedField ? Object.keys(selectedField) : [],
webType: selectedField?.webType,
2025-09-26 13:52:32 +09:00
inputType: selectedField?.inputType,
connectionId: selectedField?.connectionId,
dataType: selectedField?.dataType,
isCodeField: isCodeField,
fieldCodes: fieldCodes,
availableCodesKeys: Object.keys(availableCodes),
});
if (isCodeField && fieldCodes && fieldCodes.length > 0) {
// 코드 타입 필드면 코드 선택 드롭다운
return (
<Select
value={condition.value || ""}
onValueChange={(value) => {
if (value !== "__code_placeholder__") {
actions.updateControlCondition(index, {
...condition,
value: value,
});
}
}}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="코드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__code_placeholder__" disabled>
</SelectItem>
{fieldCodes.map((code, codeIndex) => {
console.log("🎨 코드 렌더링:", {
index: codeIndex,
code: code,
codeValue: code.code,
codeName: code.name,
hasCode: !!code.code,
hasName: !!code.name,
});
return (
<SelectItem
key={`code_${condition.field}_${code.code || codeIndex}_${codeIndex}`}
value={code.code || `unknown_${codeIndex}`}
>
2025-09-26 13:52:32 +09:00
{code.name}
</SelectItem>
);
})}
</SelectContent>
</Select>
);
} else {
// 일반 필드면 텍스트 입력
return (
<Input
placeholder="값"
value={condition.value || ""}
onChange={(e) =>
actions.updateControlCondition(index, {
...condition,
value: e.target.value,
})
}
className="w-32"
/>
);
}
})()}
{/* 삭제 버튼 */}
<Button
variant="ghost"
size="sm"
onClick={() => actions.deleteControlCondition(index)}
className="text-destructive hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* 로딩 상태 */}
{isLoading && (
<div className="flex items-center justify-center py-8">
<div className="text-muted-foreground"> ...</div>
</div>
)}
{/* 조건 없음 안내 */}
2025-09-26 13:52:32 +09:00
{!isLoading && fromColumns.length > 0 && toColumns.length > 0 && controlConditions.length === 0 && (
<div className="rounded-lg border-2 border-dashed p-8 text-center">
<AlertCircle className="text-muted-foreground mx-auto mb-3 h-8 w-8" />
<h3 className="mb-2 text-lg font-medium"> </h3>
<p className="text-muted-foreground mb-4">
.
<br />
.
</p>
<Button
onClick={() => {
console.log("제어 조건 추가 버튼 클릭");
actions.addControlCondition();
}}
variant="outline"
>
</Button>
</div>
)}
{/* 컬럼 정보 로드 실패 시 안내 */}
2025-09-26 13:52:32 +09:00
{fromColumns.length === 0 && toColumns.length === 0 && controlConditions.length === 0 && (
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
<h4 className="mb-2 text-sm font-medium text-yellow-800"> </h4>
<div className="space-y-2 text-sm text-yellow-700">
<p> </p>
<p> </p>
<p> </p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
console.log("🔄 수동 조건 추가");
actions.addControlCondition();
}}
className="mt-3 flex items-center gap-2"
>
<Plus className="h-4 w-4" />
</Button>
</div>
)}
{/* 설정 요약 */}
{controlConditions.length > 0 && (
<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={controlConditions.length > 0 ? "default" : "secondary"}>
{controlConditions.length > 0 ? `${controlConditions.length}개 조건` : "조건 없음"}
</Badge>
</div>
<div className="flex justify-between text-sm">
<span> :</span>
<span className="text-muted-foreground">
{controlConditions.length === 0 ? "항상 실행" : "조건부 실행"}
</span>
</div>
</div>
</div>
)}
</div>
{/* 하단 네비게이션 */}
<div className="flex-shrink-0 border-t bg-white p-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>
<Button onClick={onNext} disabled={!canProceed} className="flex items-center gap-2">
다음: 액션
<CheckCircle className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</>
);
};
export default ControlConditionStep;