Enhance batch management functionality by adding node flow execution support and improving batch configuration creation. Introduce new API endpoint for retrieving node flows and update existing batch services to handle execution types. Update frontend components to support new scheduling options and node flow selection.

This commit is contained in:
DDD1542
2026-03-19 15:07:07 +09:00
parent 7f781b0177
commit 43cf91e748
41 changed files with 4020 additions and 3155 deletions

View File

@@ -1,132 +1,88 @@
"use client";
/**
* 조건 분기 노드
*/
import { memo } from "react";
import { Handle, Position, NodeProps } from "reactflow";
import { Zap, Check, X } from "lucide-react";
import { GitBranch } from "lucide-react";
import { CompactNodeShell } from "./CompactNodeShell";
import type { ConditionNodeData } from "@/types/node-editor";
const OPERATOR_LABELS: Record<string, string> = {
EQUALS: "=",
NOT_EQUALS: "",
GREATER_THAN: ">",
LESS_THAN: "<",
GREATER_THAN_OR_EQUAL: "",
LESS_THAN_OR_EQUAL: "≤",
LIKE: "포함",
NOT_LIKE: "미포함",
IN: "IN",
NOT_IN: "NOT IN",
IS_NULL: "NULL",
IS_NOT_NULL: "NOT NULL",
EXISTS_IN: "EXISTS IN",
NOT_EXISTS_IN: "NOT EXISTS IN",
};
// EXISTS 계열 연산자인지 확인
const isExistsOperator = (operator: string): boolean => {
return operator === "EXISTS_IN" || operator === "NOT_EXISTS_IN";
EQUALS: "=", NOT_EQUALS: "!=",
GREATER_THAN: ">", LESS_THAN: "<",
GREATER_THAN_OR_EQUAL: ">=", LESS_THAN_OR_EQUAL: "<=",
LIKE: "포함", NOT_LIKE: "미포함",
IN: "IN", NOT_IN: "NOT IN",
IS_NULL: "NULL", IS_NOT_NULL: "NOT NULL",
EXISTS_IN: "EXISTS", NOT_EXISTS_IN: "NOT EXISTS",
};
export const ConditionNode = memo(({ data, selected }: NodeProps<ConditionNodeData>) => {
const condCount = data.conditions?.length || 0;
const summary = condCount > 0
? `${condCount}개 조건 (${data.logic || "AND"})`
: "조건을 설정해 주세요";
return (
<div
className={`min-w-[280px] rounded-lg border-2 bg-white shadow-md transition-all ${
selected ? "border-yellow-500 shadow-lg" : "border-border"
className={`rounded-lg border bg-zinc-900 shadow-lg transition-all ${
selected ? "border-violet-500 shadow-violet-500/20" : "border-zinc-700"
}`}
style={{ minWidth: "260px", maxWidth: "320px" }}
>
{/* 입력 핸들 */}
<Handle type="target" position={Position.Left} className="!h-3 !w-3 !border-2 !border-yellow-500 !bg-white" />
<Handle
type="target"
position={Position.Left}
className="!h-2.5 !w-2.5 !border-2 !border-amber-500 !bg-zinc-900"
/>
{/* 헤더 */}
<div className="flex items-center gap-2 rounded-t-lg bg-amber-500 px-3 py-2 text-white">
<Zap className="h-4 w-4" />
<div className="flex-1">
<div className="text-sm font-semibold"> </div>
<div className="text-xs opacity-80">{data.displayName || "조건 분기"}</div>
<div className="flex items-center gap-2.5 px-3 py-2.5">
<div className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-amber-500/20">
<GitBranch className="h-3.5 w-3.5 text-amber-400" />
</div>
<div className="min-w-0 flex-1">
<div className="text-xs font-semibold text-zinc-200">
{data.displayName || "조건 분기"}
</div>
<div className="line-clamp-2 text-[10px] leading-relaxed text-zinc-500">{summary}</div>
</div>
</div>
{/* 본문 */}
<div className="p-3">
{data.conditions && data.conditions.length > 0 ? (
<div className="space-y-2">
<div className="text-xs font-medium text-foreground">: ({data.conditions.length})</div>
<div className="max-h-[150px] space-y-1.5 overflow-y-auto">
{data.conditions.slice(0, 4).map((condition, idx) => (
<div key={idx} className="rounded bg-amber-50 px-2 py-1.5 text-xs">
{idx > 0 && (
<div className="mb-1 text-center text-xs font-semibold text-amber-600">{data.logic}</div>
)}
<div className="flex flex-wrap items-center gap-1">
<span className="font-mono text-foreground">{condition.field}</span>
<span
className={`rounded px-1 py-0.5 ${
isExistsOperator(condition.operator)
? "bg-purple-200 text-purple-800"
: "bg-yellow-200 text-yellow-800"
}`}
>
{OPERATOR_LABELS[condition.operator] || condition.operator}
</span>
{/* EXISTS 연산자인 경우 테이블.필드 표시 */}
{isExistsOperator(condition.operator) ? (
<span className="text-purple-600">
{(condition as any).lookupTableLabel || (condition as any).lookupTable || "..."}
{(condition as any).lookupField && `.${(condition as any).lookupFieldLabel || (condition as any).lookupField}`}
</span>
) : (
// 일반 연산자인 경우 값 표시
condition.value !== null &&
condition.value !== undefined && (
<span className="text-muted-foreground">
{typeof condition.value === "string" ? `"${condition.value}"` : String(condition.value)}
</span>
)
)}
</div>
</div>
))}
{data.conditions.length > 4 && (
<div className="text-xs text-muted-foreground/70">... {data.conditions.length - 4}</div>
{/* 조건 미리보기 */}
{condCount > 0 && (
<div className="space-y-0.5 border-t border-zinc-800 px-3 py-2 text-[10px] text-zinc-400">
{data.conditions!.slice(0, 2).map((c, i) => (
<div key={i} className="flex items-center gap-1 flex-wrap">
{i > 0 && <span className="text-amber-500">{data.logic}</span>}
<span className="font-mono text-zinc-300">{c.field}</span>
<span className="text-amber-400">{OPERATOR_LABELS[c.operator] || c.operator}</span>
{c.value !== undefined && c.value !== null && (
<span className="text-zinc-500">{String(c.value)}</span>
)}
</div>
</div>
) : (
<div className="text-center text-xs text-muted-foreground/70"> </div>
)}
</div>
))}
{condCount > 2 && <span className="text-zinc-600"> {condCount - 2}</span>}
</div>
)}
{/* 분기 출력 핸들 */}
<div className="relative border-t">
{/* TRUE 출력 - 오른쪽 위 */}
<div className="relative border-b p-2">
<div className="flex items-center justify-end gap-1 pr-6 text-xs">
<Check className="h-3 w-3 text-emerald-600" />
<span className="font-medium text-emerald-600">TRUE</span>
</div>
{/* 분기 출력 */}
<div className="border-t border-zinc-800">
<div className="relative flex items-center justify-end px-3 py-1.5">
<span className="text-[10px] font-medium text-emerald-400"></span>
<Handle
type="source"
position={Position.Right}
id="true"
className="!top-1/2 !-right-1.5 !h-3 !w-3 !-translate-y-1/2 !border-2 !border-emerald-500 !bg-white"
className="!h-2.5 !w-2.5 !border-2 !border-emerald-500 !bg-zinc-900"
/>
</div>
{/* FALSE 출력 - 오른쪽 아래 */}
<div className="relative p-2">
<div className="flex items-center justify-end gap-1 pr-6 text-xs">
<X className="h-3 w-3 text-destructive" />
<span className="font-medium text-destructive">FALSE</span>
</div>
<div className="relative flex items-center justify-end border-t border-zinc-800/50 px-3 py-1.5">
<span className="text-[10px] font-medium text-pink-400"></span>
<Handle
type="source"
position={Position.Right}
id="false"
className="!top-1/2 !-right-1.5 !h-3 !w-3 !-translate-y-1/2 !border-2 !border-destructive !bg-white"
className="!h-2.5 !w-2.5 !border-2 !border-pink-500 !bg-zinc-900"
/>
</div>
</div>