203 lines
7.7 KiB
TypeScript
203 lines
7.7 KiB
TypeScript
"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[];
|
||
toTableColumns: ColumnInfo[];
|
||
fromTableName?: string;
|
||
toTableName?: string;
|
||
}
|
||
|
||
export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = ({
|
||
action,
|
||
actionIndex,
|
||
settings,
|
||
onSettingsChange,
|
||
fromTableColumns,
|
||
toTableColumns,
|
||
fromTableName,
|
||
toTableName,
|
||
}) => {
|
||
const { addActionGroupStart, addActionGroupEnd, getActionCurrentGroupLevel } = useActionConditionHelpers();
|
||
|
||
// 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;
|
||
|
||
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",
|
||
tableType: undefined, // 사용자가 직접 선택하도록
|
||
// 첫 번째 조건이 아니고, 바로 앞이 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">
|
||
<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
|
||
? "border-red-300 bg-destructive/10 text-red-700"
|
||
: "border-gray-200 text-gray-700"
|
||
}`}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
🔍 이 액션의 실행 조건
|
||
{isConditionRequired ? (
|
||
<span className="rounded bg-destructive/20 px-1 py-0.5 text-xs font-semibold text-red-700">필수</span>
|
||
) : (
|
||
<span className="text-gray-500">(선택사항)</span>
|
||
)}
|
||
{action.conditions && action.conditions.length > 0 && (
|
||
<span className="rounded-full bg-primary/20 px-2 py-0.5 text-xs text-blue-700">
|
||
{action.conditions.length}개
|
||
</span>
|
||
)}
|
||
{isConditionRequired && !hasValidConditions && (
|
||
<span className="rounded bg-destructive/20 px-1 py-0.5 text-xs text-destructive">⚠️ 조건 필요</span>
|
||
)}
|
||
</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>
|
||
|
||
{/* 조건이 없을 때 안내 메시지 */}
|
||
{(!action.conditions || action.conditions.length === 0) && (
|
||
<div
|
||
className={`rounded border p-3 text-xs ${
|
||
isConditionRequired
|
||
? "border-destructive/20 bg-destructive/10 text-red-700"
|
||
: "border-gray-200 bg-gray-50 text-muted-foreground"
|
||
}`}
|
||
>
|
||
{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>
|
||
)}
|
||
|
||
{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}
|
||
toTableColumns={toTableColumns}
|
||
fromTableName={fromTableName}
|
||
toTableName={toTableName}
|
||
getActionCurrentGroupLevel={getActionCurrentGroupLevel}
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</details>
|
||
</div>
|
||
);
|
||
};
|