2025-09-16 15:43:18 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import React from "react";
|
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
import { Plus, Trash2 } from "lucide-react";
|
|
|
|
|
|
import { ColumnInfo } from "@/lib/api/dataflow";
|
|
|
|
|
|
import { DataSaveSettings } from "@/types/connectionTypes";
|
|
|
|
|
|
import { generateConditionId } from "@/utils/connectionUtils";
|
|
|
|
|
|
import { useActionConditionHelpers } from "@/hooks/useConditionManager";
|
|
|
|
|
|
import { ActionConditionRenderer } from "./ActionConditionRenderer";
|
|
|
|
|
|
|
|
|
|
|
|
interface ActionConditionsSectionProps {
|
|
|
|
|
|
action: DataSaveSettings["actions"][0];
|
|
|
|
|
|
actionIndex: number;
|
|
|
|
|
|
settings: DataSaveSettings;
|
|
|
|
|
|
onSettingsChange: (settings: DataSaveSettings) => void;
|
|
|
|
|
|
fromTableColumns: ColumnInfo[];
|
2025-09-18 10:05:28 +09:00
|
|
|
|
toTableColumns: ColumnInfo[];
|
|
|
|
|
|
fromTableName?: string;
|
|
|
|
|
|
toTableName?: string;
|
2025-09-16 15:43:18 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = ({
|
|
|
|
|
|
action,
|
|
|
|
|
|
actionIndex,
|
|
|
|
|
|
settings,
|
|
|
|
|
|
onSettingsChange,
|
|
|
|
|
|
fromTableColumns,
|
2025-09-18 10:05:28 +09:00
|
|
|
|
toTableColumns,
|
|
|
|
|
|
fromTableName,
|
|
|
|
|
|
toTableName,
|
2025-09-16 15:43:18 +09:00
|
|
|
|
}) => {
|
|
|
|
|
|
const { addActionGroupStart, addActionGroupEnd, getActionCurrentGroupLevel } = useActionConditionHelpers();
|
|
|
|
|
|
|
2025-09-18 13:26:42 +09:00
|
|
|
|
// INSERT가 아닌 액션 타입인지 확인
|
|
|
|
|
|
const isConditionRequired = action.actionType !== "insert";
|
|
|
|
|
|
|
|
|
|
|
|
// 유효한 조건이 있는지 확인 (group-start, group-end만 있는 경우 제외)
|
|
|
|
|
|
const hasValidConditions =
|
|
|
|
|
|
action.conditions?.some((condition) => {
|
|
|
|
|
|
if (condition.type !== "condition") return false;
|
|
|
|
|
|
if (!condition.field || !condition.operator) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// value가 null, undefined, 빈 문자열이면 유효하지 않음
|
|
|
|
|
|
const value = condition.value;
|
|
|
|
|
|
if (value === null || value === undefined || value === "") return false;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}) || false;
|
|
|
|
|
|
|
2025-09-16 15:43:18 +09:00
|
|
|
|
const addActionCondition = () => {
|
|
|
|
|
|
const newActions = [...settings.actions];
|
|
|
|
|
|
if (!newActions[actionIndex].conditions) {
|
|
|
|
|
|
newActions[actionIndex].conditions = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
const currentConditions = newActions[actionIndex].conditions || [];
|
|
|
|
|
|
const newCondition = {
|
|
|
|
|
|
id: generateConditionId(),
|
|
|
|
|
|
type: "condition" as const,
|
|
|
|
|
|
field: "",
|
|
|
|
|
|
operator: "=" as const,
|
|
|
|
|
|
value: "",
|
|
|
|
|
|
dataType: "string",
|
2025-09-18 10:05:28 +09:00
|
|
|
|
tableType: undefined, // 사용자가 직접 선택하도록
|
2025-09-16 15:43:18 +09:00
|
|
|
|
// 첫 번째 조건이 아니고, 바로 앞이 group-start가 아니면 logicalOperator 추가
|
|
|
|
|
|
...(currentConditions.length > 0 &&
|
|
|
|
|
|
currentConditions[currentConditions.length - 1]?.type !== "group-start" && {
|
|
|
|
|
|
logicalOperator: "AND" as const,
|
|
|
|
|
|
}),
|
|
|
|
|
|
};
|
|
|
|
|
|
newActions[actionIndex].conditions = [...currentConditions, newCondition];
|
|
|
|
|
|
onSettingsChange({ ...settings, actions: newActions });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const clearAllConditions = () => {
|
|
|
|
|
|
const newActions = [...settings.actions];
|
|
|
|
|
|
newActions[actionIndex].conditions = [];
|
|
|
|
|
|
onSettingsChange({ ...settings, actions: newActions });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
|
<details className="group">
|
2025-09-18 13:26:42 +09:00
|
|
|
|
<summary
|
|
|
|
|
|
className={`flex cursor-pointer items-center justify-between rounded border p-2 text-xs font-medium hover:bg-gray-50 hover:text-gray-900 ${
|
|
|
|
|
|
isConditionRequired && !hasValidConditions
|
2025-10-02 14:34:15 +09:00
|
|
|
|
? "border-red-300 bg-destructive/10 text-red-700"
|
2025-09-18 13:26:42 +09:00
|
|
|
|
: "border-gray-200 text-gray-700"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
2025-09-16 15:43:18 +09:00
|
|
|
|
<div className="flex items-center gap-2">
|
2025-09-18 13:26:42 +09:00
|
|
|
|
🔍 이 액션의 실행 조건
|
|
|
|
|
|
{isConditionRequired ? (
|
2025-10-02 14:34:15 +09:00
|
|
|
|
<span className="rounded bg-destructive/20 px-1 py-0.5 text-xs font-semibold text-red-700">필수</span>
|
2025-09-18 13:26:42 +09:00
|
|
|
|
) : (
|
|
|
|
|
|
<span className="text-gray-500">(선택사항)</span>
|
|
|
|
|
|
)}
|
2025-09-16 15:43:18 +09:00
|
|
|
|
{action.conditions && action.conditions.length > 0 && (
|
2025-10-02 14:34:15 +09:00
|
|
|
|
<span className="rounded-full bg-primary/20 px-2 py-0.5 text-xs text-blue-700">
|
2025-09-16 15:43:18 +09:00
|
|
|
|
{action.conditions.length}개
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2025-09-18 13:26:42 +09:00
|
|
|
|
{isConditionRequired && !hasValidConditions && (
|
2025-10-02 14:34:15 +09:00
|
|
|
|
<span className="rounded bg-destructive/20 px-1 py-0.5 text-xs text-destructive">⚠️ 조건 필요</span>
|
2025-09-18 13:26:42 +09:00
|
|
|
|
)}
|
2025-09-16 15:43:18 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
{action.conditions && action.conditions.length > 0 && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
clearAllConditions();
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-5 w-5 p-0 text-red-500 hover:text-red-700"
|
|
|
|
|
|
title="조건 모두 삭제"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</summary>
|
|
|
|
|
|
<div className="mt-2 space-y-2 border-l-2 border-gray-100 pl-4">
|
|
|
|
|
|
<div className="mb-2 flex items-center justify-between">
|
|
|
|
|
|
<div className="flex gap-1">
|
|
|
|
|
|
<Button size="sm" variant="outline" onClick={addActionCondition} className="h-6 text-xs">
|
|
|
|
|
|
<Plus className="mr-1 h-2 w-2" />
|
|
|
|
|
|
조건 추가
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={() => addActionGroupStart(actionIndex, settings, onSettingsChange)}
|
|
|
|
|
|
className="h-6 text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
그룹 시작 (
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={() => addActionGroupEnd(actionIndex, settings, onSettingsChange)}
|
|
|
|
|
|
className="h-6 text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
그룹 끝 )
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-18 13:26:42 +09:00
|
|
|
|
|
|
|
|
|
|
{/* 조건이 없을 때 안내 메시지 */}
|
|
|
|
|
|
{(!action.conditions || action.conditions.length === 0) && (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={`rounded border p-3 text-xs ${
|
|
|
|
|
|
isConditionRequired
|
2025-10-02 14:34:15 +09:00
|
|
|
|
? "border-destructive/20 bg-destructive/10 text-red-700"
|
|
|
|
|
|
: "border-gray-200 bg-gray-50 text-muted-foreground"
|
2025-09-18 13:26:42 +09:00
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{isConditionRequired ? (
|
|
|
|
|
|
<div className="flex items-start gap-2">
|
|
|
|
|
|
<span className="text-red-500">⚠️</span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="font-medium">실행조건이 필요합니다</div>
|
|
|
|
|
|
<div className="mt-1">
|
|
|
|
|
|
{action.actionType.toUpperCase()} 액션은 언제 실행될지 결정하는 조건이 반드시 필요합니다.
|
|
|
|
|
|
<br />
|
|
|
|
|
|
<strong>필드, 연산자, 값</strong>을 모두 입력해야 합니다.
|
|
|
|
|
|
<br />
|
|
|
|
|
|
예: user_name = '관리자우저' AND status = 'active'
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div>조건이 설정되지 않았습니다. INSERT 액션은 조건 없이도 실행 가능합니다.</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-09-16 15:43:18 +09:00
|
|
|
|
{action.conditions && action.conditions.length > 0 && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{action.conditions.map((condition, condIndex) => (
|
|
|
|
|
|
<div key={`action-${actionIndex}-condition-${condition.id}`}>
|
|
|
|
|
|
<ActionConditionRenderer
|
|
|
|
|
|
condition={condition}
|
|
|
|
|
|
condIndex={condIndex}
|
|
|
|
|
|
actionIndex={actionIndex}
|
|
|
|
|
|
settings={settings}
|
|
|
|
|
|
onSettingsChange={onSettingsChange}
|
|
|
|
|
|
fromTableColumns={fromTableColumns}
|
2025-09-18 10:05:28 +09:00
|
|
|
|
toTableColumns={toTableColumns}
|
|
|
|
|
|
fromTableName={fromTableName}
|
|
|
|
|
|
toTableName={toTableName}
|
2025-09-16 15:43:18 +09:00
|
|
|
|
getActionCurrentGroupLevel={getActionCurrentGroupLevel}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|