ERP-node/frontend/components/dataflow/connection/ActionConditionsSection.tsx

203 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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-red-50 text-red-700"
: "border-gray-200 text-gray-700"
}`}
>
<div className="flex items-center gap-2">
🔍
{isConditionRequired ? (
<span className="rounded bg-red-100 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-blue-100 px-2 py-0.5 text-xs text-blue-700">
{action.conditions.length}
</span>
)}
{isConditionRequired && !hasValidConditions && (
<span className="rounded bg-red-100 px-1 py-0.5 text-xs text-red-600"> </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-red-200 bg-red-50 text-red-700"
: "border-gray-200 bg-gray-50 text-gray-600"
}`}
>
{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>
);
};