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

788 lines
34 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 { Plus, Trash2, Settings } from "lucide-react";
// 타입 import
import { ColumnInfo } from "@/lib/types/multiConnection";
import { getCodesForColumn, CodeItem } from "@/lib/api/codeManagement";
interface ActionCondition {
id: string;
field: string;
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN" | "IS NULL" | "IS NOT NULL";
value: string;
valueType?: "static" | "field" | "calculated"; // 값 타입 (고정값, 필드값, 계산값)
logicalOperator?: "AND" | "OR";
}
interface FieldValueMapping {
id: string;
targetField: string;
valueType: "static" | "source_field" | "code" | "calculated";
value: string;
sourceField?: string;
codeCategory?: string;
}
interface ActionConditionBuilderProps {
actionType: "insert" | "update" | "delete" | "upsert";
fromColumns: ColumnInfo[];
toColumns: ColumnInfo[];
conditions: ActionCondition[];
fieldMappings: FieldValueMapping[];
2025-09-26 13:52:32 +09:00
columnMappings?: any[]; // 컬럼 매핑 정보 (이미 매핑된 필드들)
onConditionsChange: (conditions: ActionCondition[]) => void;
onFieldMappingsChange: (mappings: FieldValueMapping[]) => void;
showFieldMappings?: boolean; // 필드 매핑 섹션 표시 여부
}
/**
* 🎯
* - (WHERE )
* - (SET )
* -
*/
const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
actionType,
fromColumns,
toColumns,
conditions,
fieldMappings,
2025-09-26 13:52:32 +09:00
columnMappings = [],
onConditionsChange,
onFieldMappingsChange,
showFieldMappings = true,
}) => {
const [availableCodes, setAvailableCodes] = useState<Record<string, CodeItem[]>>({});
2025-09-26 13:52:32 +09:00
// 컬럼 매핑인지 필드값 매핑인지 구분하는 함수
const isColumnMapping = (mapping: any): boolean => {
return mapping.fromField && mapping.toField && mapping.fromField.columnName && mapping.toField.columnName;
};
// 이미 컬럼 매핑된 필드들을 가져오는 함수
const getMappedFieldNames = (): string[] => {
if (!columnMappings || columnMappings.length === 0) return [];
return columnMappings.filter((mapping) => isColumnMapping(mapping)).map((mapping) => mapping.toField.columnName);
};
// 매핑되지 않은 필드들만 필터링하는 함수
const getUnmappedToColumns = (): ColumnInfo[] => {
const mappedFieldNames = getMappedFieldNames();
return toColumns.filter((column) => !mappedFieldNames.includes(column.columnName));
};
// 필드값 설정에서 사용 가능한 필드들 (이미 필드값 설정에서 사용된 필드 제외)
const getAvailableFieldsForMapping = (currentIndex?: number): ColumnInfo[] => {
const unmappedColumns = getUnmappedToColumns();
const usedFieldNames = fieldMappings
.filter((_, index) => index !== currentIndex) // 현재 편집 중인 항목 제외
.map((mapping) => mapping.targetField)
.filter((field) => field); // 빈 값 제외
return unmappedColumns.filter((column) => !usedFieldNames.includes(column.columnName));
};
const operators = [
{ value: "=", label: "같음 (=)" },
{ value: "!=", label: "다름 (!=)" },
{ value: ">", label: "큼 (>)" },
{ value: "<", label: "작음 (<)" },
{ value: ">=", label: "크거나 같음 (>=)" },
{ value: "<=", label: "작거나 같음 (<=)" },
{ value: "LIKE", label: "포함 (LIKE)" },
{ value: "IN", label: "목록 중 하나 (IN)" },
{ value: "IS NULL", label: "빈 값 (IS NULL)" },
{ value: "IS NOT NULL", label: "값 있음 (IS NOT NULL)" },
];
// 코드 정보 로드
useEffect(() => {
const loadCodes = async () => {
2025-09-26 13:52:32 +09:00
const codeFields = [...fromColumns, ...toColumns].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;
});
console.log(
"🔍 ActionConditionBuilder - 모든 컬럼 정보:",
[...fromColumns, ...toColumns].map((col) => ({
columnName: col.columnName,
connectionId: col.connectionId,
inputType: col.inputType,
webType: col.webType,
})),
);
2025-09-26 13:52:32 +09:00
console.log("🔍 ActionConditionBuilder - 코드 타입 컬럼들:", codeFields);
for (const field of codeFields) {
try {
const codes = await getCodesForColumn(field.columnName, field.webType, field.codeCategory);
if (codes.length > 0) {
setAvailableCodes((prev) => ({
...prev,
[field.columnName]: codes,
}));
}
} catch (error) {
console.error(`코드 로드 실패: ${field.columnName}`, error);
}
}
};
if (fromColumns.length > 0 || toColumns.length > 0) {
loadCodes();
}
}, [fromColumns, toColumns]);
2025-09-26 13:52:32 +09:00
// 컬럼 매핑이 변경될 때 필드값 설정에서 이미 매핑된 필드들 제거
useEffect(() => {
const mappedFieldNames = getMappedFieldNames();
if (mappedFieldNames.length > 0) {
const updatedFieldMappings = fieldMappings.filter((mapping) => !mappedFieldNames.includes(mapping.targetField));
// 변경된 내용이 있으면 업데이트
if (updatedFieldMappings.length !== fieldMappings.length) {
console.log("🧹 매핑된 필드들을 필드값 설정에서 제거:", {
removed: fieldMappings.filter((mapping) => mappedFieldNames.includes(mapping.targetField)),
remaining: updatedFieldMappings,
});
onFieldMappingsChange(updatedFieldMappings);
}
}
}, [columnMappings]); // columnMappings 변경 시에만 실행
// 조건 추가
const addCondition = () => {
const newCondition: ActionCondition = {
id: Date.now().toString(),
field: "",
operator: "=",
value: "",
...(conditions.length > 0 && { logicalOperator: "AND" }),
};
onConditionsChange([...conditions, newCondition]);
};
// 조건 업데이트
const updateCondition = (index: number, updates: Partial<ActionCondition>) => {
const updatedConditions = conditions.map((condition, i) =>
i === index ? { ...condition, ...updates } : condition,
);
onConditionsChange(updatedConditions);
};
// 조건 삭제
const deleteCondition = (index: number) => {
const updatedConditions = conditions.filter((_, i) => i !== index);
onConditionsChange(updatedConditions);
};
// 필드 매핑 추가
const addFieldMapping = () => {
2025-09-26 13:52:32 +09:00
// 임시로 검증을 완화 - 매핑되지 않은 필드가 있으면 추가 허용
const unmappedColumns = getUnmappedToColumns();
console.log("🔍 필드 추가 시도:", {
unmappedColumns,
unmappedColumnsCount: unmappedColumns.length,
fieldMappings,
columnMappings,
});
if (unmappedColumns.length === 0) {
console.warn("매핑되지 않은 필드가 없습니다.");
return;
}
const newMapping: FieldValueMapping = {
id: Date.now().toString(),
targetField: "",
valueType: "static",
value: "",
};
2025-09-26 13:52:32 +09:00
console.log("✅ 새 필드 매핑 추가:", newMapping);
onFieldMappingsChange([...fieldMappings, newMapping]);
};
// 필드 매핑 업데이트
const updateFieldMapping = (index: number, updates: Partial<FieldValueMapping>) => {
const updatedMappings = fieldMappings.map((mapping, i) => (i === index ? { ...mapping, ...updates } : mapping));
onFieldMappingsChange(updatedMappings);
};
// 필드 매핑 삭제
const deleteFieldMapping = (index: number) => {
const updatedMappings = fieldMappings.filter((_, i) => i !== index);
onFieldMappingsChange(updatedMappings);
};
// 필드의 값 입력 컴포넌트 렌더링
const renderValueInput = (mapping: FieldValueMapping, index: number, targetColumn?: ColumnInfo) => {
2025-09-26 13:52:32 +09:00
if (
mapping.valueType === "code" &&
(targetColumn?.connectionId === 0 || targetColumn?.connectionId === undefined) &&
targetColumn?.inputType === "code"
) {
const codes = availableCodes[targetColumn.columnName] || [];
return (
<Select value={mapping.value} onValueChange={(value) => updateFieldMapping(index, { value })}>
<SelectTrigger>
<SelectValue placeholder="코드 선택" />
</SelectTrigger>
<SelectContent>
{codes.map((code) => (
<SelectItem key={code.code} value={code.code}>
2025-09-26 13:52:32 +09:00
{code.name}
</SelectItem>
))}
</SelectContent>
</Select>
);
}
if (mapping.valueType === "source_field") {
return (
<Select
value={mapping.sourceField || ""}
onValueChange={(value) => updateFieldMapping(index, { sourceField: value })}
>
<SelectTrigger>
<SelectValue placeholder="소스 필드 선택" />
</SelectTrigger>
<SelectContent>
{/* FROM 테이블 필드들 */}
{fromColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM </div>
{fromColumns.map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-blue-600">📤</span>
<span>{column.displayName || column.columnName}</span>
<Badge variant="outline" className="text-xs">
{column.webType || column.dataType}
</Badge>
</div>
</SelectItem>
))}
</>
)}
{/* TO 테이블 필드들 */}
{toColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO </div>
{toColumns.map((column) => (
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-green-600">📥</span>
<span>{column.displayName || column.columnName}</span>
<Badge variant="outline" className="text-xs">
{column.webType || column.dataType}
</Badge>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
);
}
2025-09-26 13:52:32 +09:00
// 날짜 타입에 대한 특별 처리
if (
targetColumn?.webType === "date" ||
targetColumn?.webType === "datetime" ||
targetColumn?.dataType?.toLowerCase().includes("date")
) {
return (
<div className="space-y-2">
{/* 날짜 타입 선택 */}
<Select
value={mapping.value?.startsWith("#") ? mapping.value : "#custom"}
onValueChange={(value) => {
if (value === "#custom") {
updateFieldMapping(index, { value: "" });
} else {
updateFieldMapping(index, { value });
}
}}
>
<SelectTrigger>
<SelectValue placeholder="날짜 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="#NOW">🕐 (NOW)</SelectItem>
<SelectItem value="#TODAY">📅 (TODAY)</SelectItem>
<SelectItem value="#YESTERDAY">📅 </SelectItem>
<SelectItem value="#TOMORROW">📅 </SelectItem>
<SelectItem value="#WEEK_START">📅 </SelectItem>
<SelectItem value="#MONTH_START">📅 </SelectItem>
<SelectItem value="#YEAR_START">📅 </SelectItem>
<SelectItem value="#custom"> </SelectItem>
</SelectContent>
</Select>
{/* 직접 입력이 선택된 경우 */}
{(!mapping.value?.startsWith("#") || mapping.value === "#custom") && (
<div className="space-y-2">
<Input
type={targetColumn?.webType === "datetime" ? "datetime-local" : "date"}
placeholder="날짜 입력"
value={mapping.value?.startsWith("#") ? "" : mapping.value}
onChange={(e) => updateFieldMapping(index, { value: e.target.value })}
/>
<div className="text-muted-foreground text-xs">
: +7D (7 ), -30D (30 ), +1M (1 ), +1Y (1 )
</div>
</div>
)}
{/* 선택된 날짜 타입에 대한 설명 */}
{mapping.value?.startsWith("#") && mapping.value !== "#custom" && (
<div className="text-muted-foreground rounded bg-blue-50 p-2 text-xs">
{mapping.value === "#NOW" && "⏰ 현재 날짜와 시간이 저장됩니다"}
{mapping.value === "#TODAY" && "📅 현재 날짜 (00:00:00)가 저장됩니다"}
{mapping.value === "#YESTERDAY" && "📅 어제 날짜가 저장됩니다"}
{mapping.value === "#TOMORROW" && "📅 내일 날짜가 저장됩니다"}
{mapping.value === "#WEEK_START" && "📅 이번 주 월요일이 저장됩니다"}
{mapping.value === "#MONTH_START" && "📅 이번 달 1일이 저장됩니다"}
{mapping.value === "#YEAR_START" && "📅 올해 1월 1일이 저장됩니다"}
</div>
)}
</div>
);
}
// 숫자 타입에 대한 특별 처리
if (
targetColumn?.webType === "number" ||
targetColumn?.webType === "decimal" ||
targetColumn?.dataType?.toLowerCase().includes("int") ||
targetColumn?.dataType?.toLowerCase().includes("decimal") ||
targetColumn?.dataType?.toLowerCase().includes("numeric")
) {
return (
<div className="space-y-2">
{/* 숫자 타입 선택 */}
<Select
value={mapping.value?.startsWith("#") ? mapping.value : "#custom"}
onValueChange={(value) => {
if (value === "#custom") {
updateFieldMapping(index, { value: "" });
} else {
updateFieldMapping(index, { value });
}
}}
>
<SelectTrigger>
<SelectValue placeholder="숫자 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="#AUTO_INCREMENT">🔢 (AUTO_INCREMENT)</SelectItem>
<SelectItem value="#RANDOM_INT">🎲 (1-1000)</SelectItem>
<SelectItem value="#ZERO">0 0</SelectItem>
<SelectItem value="#ONE">1 1</SelectItem>
<SelectItem value="#SEQUENCE">📈 퀀</SelectItem>
<SelectItem value="#custom"> </SelectItem>
</SelectContent>
</Select>
{/* 직접 입력이 선택된 경우 */}
{(!mapping.value?.startsWith("#") || mapping.value === "#custom") && (
<Input
type="number"
placeholder="숫자 입력"
value={mapping.value?.startsWith("#") ? "" : mapping.value}
onChange={(e) => updateFieldMapping(index, { value: e.target.value })}
/>
)}
{/* 선택된 숫자 타입에 대한 설명 */}
{mapping.value?.startsWith("#") && mapping.value !== "#custom" && (
<div className="text-muted-foreground rounded bg-green-50 p-2 text-xs">
{mapping.value === "#AUTO_INCREMENT" && "🔢 데이터베이스에서 자동으로 증가하는 값이 할당됩니다"}
{mapping.value === "#RANDOM_INT" && "🎲 1부터 1000 사이의 랜덤한 정수가 생성됩니다"}
{mapping.value === "#ZERO" && "0⃣ 0 값이 저장됩니다"}
{mapping.value === "#ONE" && "1⃣ 1 값이 저장됩니다"}
{mapping.value === "#SEQUENCE" && "📈 시퀀스에서 다음 값을 가져옵니다"}
</div>
)}
</div>
);
}
return (
<Input
placeholder="값 입력"
value={mapping.value}
onChange={(e) => updateFieldMapping(index, { value: e.target.value })}
/>
);
};
return (
<div className="space-y-6">
{/* 실행 조건 설정 */}
{actionType !== "insert" && (
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center justify-between text-base">
<span> (WHERE)</span>
<Button variant="outline" size="sm" onClick={addCondition}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{conditions.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">
{actionType.toUpperCase()}
</p>
</div>
) : (
conditions.map((condition, index) => (
<div key={condition.id} className="flex items-center gap-3 rounded-lg border p-3">
{/* 논리 연산자 */}
{index > 0 && (
<Select
value={condition.logicalOperator || "AND"}
onValueChange={(value) => updateCondition(index, { logicalOperator: value as "AND" | "OR" })}
>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AND">AND</SelectItem>
<SelectItem value="OR">OR</SelectItem>
</SelectContent>
</Select>
)}
{/* 필드 선택 */}
<Select value={condition.field} onValueChange={(value) => updateCondition(index, { field: value })}>
<SelectTrigger className="w-40">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
{/* FROM 테이블 컬럼들 */}
{fromColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">FROM </div>
{fromColumns.map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-blue-600">📤</span>
<span>{column.displayName || column.columnName}</span>
</div>
</SelectItem>
))}
</>
)}
{/* TO 테이블 컬럼들 */}
{toColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO </div>
{toColumns.map((column) => (
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-green-600">📥</span>
<span>{column.displayName || column.columnName}</span>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
{/* 연산자 선택 */}
<Select
value={condition.operator}
onValueChange={(value) => updateCondition(index, { operator: value as any })}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
{operators.map((op) => (
<SelectItem key={op.value} value={op.value}>
{op.label}
</SelectItem>
))}
</SelectContent>
</Select>
{/* 값 입력 */}
{!["IS NULL", "IS NOT NULL"].includes(condition.operator) &&
(() => {
// FROM/TO 테이블 컬럼 구분
let fieldColumn;
let actualFieldName;
if (condition.field?.startsWith("from.")) {
actualFieldName = condition.field.replace("from.", "");
fieldColumn = fromColumns.find((col) => col.columnName === actualFieldName);
} else if (condition.field?.startsWith("to.")) {
actualFieldName = condition.field.replace("to.", "");
fieldColumn = toColumns.find((col) => col.columnName === actualFieldName);
} else {
// 기존 호환성을 위해 TO 테이블에서 먼저 찾기
actualFieldName = condition.field;
fieldColumn =
toColumns.find((col) => col.columnName === condition.field) ||
fromColumns.find((col) => col.columnName === condition.field);
}
const fieldCodes = availableCodes[actualFieldName];
// 코드 타입 필드면 코드 선택
if (fieldColumn?.webType === "code" && fieldCodes?.length > 0) {
return (
<Select value={condition.value} onValueChange={(value) => updateCondition(index, { value })}>
<SelectTrigger className="flex-1">
<SelectValue placeholder="코드 선택" />
</SelectTrigger>
<SelectContent>
{fieldCodes.map((code) => (
<SelectItem key={code.code} value={code.code}>
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">
{code.code}
</Badge>
<span>{code.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
);
}
// 값 타입 선택 (고정값, 다른 필드 값, 계산식 등)
return (
<div className="flex flex-1 gap-2">
{/* 값 타입 선택 */}
<Select
value={condition.valueType || "static"}
onValueChange={(valueType) => updateCondition(index, { valueType, value: "" })}
>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"></SelectItem>
<SelectItem value="field"></SelectItem>
<SelectItem value="calculated"></SelectItem>
</SelectContent>
</Select>
{/* 값 입력 */}
{condition.valueType === "field" ? (
<Select
value={condition.value}
onValueChange={(value) => updateCondition(index, { value })}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
{/* FROM 테이블 필드들 */}
{fromColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">
FROM
</div>
{fromColumns.map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-blue-600">📤</span>
<span>{column.displayName || column.columnName}</span>
</div>
</SelectItem>
))}
</>
)}
{/* TO 테이블 필드들 */}
{toColumns.length > 0 && (
<>
<div className="text-muted-foreground px-2 py-1 text-xs font-medium">TO </div>
{toColumns.map((column) => (
<SelectItem key={`to_${column.columnName}`} value={`to.${column.columnName}`}>
<div className="flex items-center gap-2">
<span className="text-green-600">📥</span>
<span>{column.displayName || column.columnName}</span>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
) : (
<Input
placeholder={condition.valueType === "calculated" ? "계산식 입력" : "값 입력"}
value={condition.value}
onChange={(e) => updateCondition(index, { value: e.target.value })}
className="flex-1"
/>
)}
</div>
);
})()}
{/* 삭제 버튼 */}
<Button variant="ghost" size="sm" onClick={() => deleteCondition(index)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))
)}
</CardContent>
</Card>
)}
{/* 필드 값 매핑 설정 */}
{showFieldMappings && actionType !== "delete" && (
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center justify-between text-base">
2025-09-26 13:52:32 +09:00
<div>
<span> (SET)</span>
<p className="text-muted-foreground mt-1 text-xs"> </p>
</div>
<Button
variant="outline"
size="sm"
onClick={addFieldMapping}
disabled={getUnmappedToColumns().length === 0}
>
<Plus className="mr-2 h-4 w-4" />
</Button>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
2025-09-26 13:52:32 +09:00
{/* 매핑되지 않은 필드가 없는 경우 */}
{getUnmappedToColumns().length === 0 ? (
<div className="rounded-lg border bg-green-50 p-4 text-center">
<div className="mb-2 text-green-600"> </div>
<p className="text-sm text-green-700">
TO .
</p>
</div>
) : fieldMappings.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" />
2025-09-26 13:52:32 +09:00
<p className="text-muted-foreground text-sm"> </p>
<p className="text-muted-foreground mt-1 text-xs">
</p>
<p className="text-muted-foreground mt-2 text-xs">
{getUnmappedToColumns().length}
</p>
</div>
) : (
2025-09-26 13:52:32 +09:00
(() => {
console.log("🎨 필드값 설정 렌더링:", {
fieldMappings,
fieldMappingsCount: fieldMappings.length,
});
return fieldMappings.map((mapping, index) => {
const targetColumn = toColumns.find((col) => col.columnName === mapping.targetField);
return (
<div key={mapping.id} className="flex items-center gap-3 rounded-lg border p-3">
{/* 대상 필드 */}
<Select
value={mapping.targetField}
onValueChange={(value) =>
updateFieldMapping(index, {
targetField: value,
value: "", // 필드 변경 시 값 초기화
sourceField: "", // 소스 필드도 초기화
})
}
>
<SelectTrigger className="w-40">
<SelectValue placeholder="대상 필드" />
</SelectTrigger>
<SelectContent>
{getAvailableFieldsForMapping(index).map((column) => (
<SelectItem key={column.columnName} value={column.columnName}>
<div className="flex items-center gap-2">
<span>{column.displayName || column.columnName}</span>
<Badge variant="outline" className="text-xs">
{column.webType || column.dataType}
</Badge>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{/* 값 타입 */}
<Select
value={mapping.valueType}
onValueChange={(value) =>
updateFieldMapping(index, {
valueType: value as any,
value: "", // 값 타입 변경 시 값 초기화
sourceField: "", // 소스 필드도 초기화
})
}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"></SelectItem>
<SelectItem value="source_field"></SelectItem>
{(targetColumn?.connectionId === 0 || targetColumn?.connectionId === undefined) &&
targetColumn?.inputType === "code" && <SelectItem value="code"></SelectItem>}
<SelectItem value="calculated"></SelectItem>
</SelectContent>
</Select>
{/* 값 입력 */}
<div className="flex-1">{renderValueInput(mapping, index, targetColumn)}</div>
{/* 삭제 버튼 */}
<Button variant="ghost" size="sm" onClick={() => deleteFieldMapping(index)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
);
});
})()
)}
</CardContent>
</Card>
)}
</div>
);
};
export default ActionConditionBuilder;