216 lines
9.3 KiB
TypeScript
216 lines
9.3 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Trash2 } from "lucide-react";
|
|
import { ConditionNode, ColumnInfo } from "@/lib/api/dataflow";
|
|
import { getInputTypeForDataType } from "@/utils/connectionUtils";
|
|
|
|
interface ConditionRendererProps {
|
|
conditions: ConditionNode[];
|
|
fromTableColumns: ColumnInfo[];
|
|
onUpdateCondition: (index: number, field: keyof ConditionNode, value: string) => void;
|
|
onRemoveCondition: (index: number) => void;
|
|
getCurrentGroupLevel: (index: number) => number;
|
|
}
|
|
|
|
export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
|
|
conditions,
|
|
fromTableColumns,
|
|
onUpdateCondition,
|
|
onRemoveCondition,
|
|
getCurrentGroupLevel,
|
|
}) => {
|
|
const renderConditionValue = (condition: ConditionNode, index: number) => {
|
|
const selectedColumn = fromTableColumns.find((col) => col.columnName === condition.field);
|
|
const dataType = selectedColumn?.dataType?.toLowerCase() || "string";
|
|
const inputType = getInputTypeForDataType(dataType);
|
|
|
|
if (dataType.includes("bool")) {
|
|
return (
|
|
<Select
|
|
value={String(condition.value || "")}
|
|
onValueChange={(value) => onUpdateCondition(index, "value", value)}
|
|
>
|
|
<SelectTrigger className="h-8 flex-1 text-xs">
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="true">TRUE</SelectItem>
|
|
<SelectItem value="false">FALSE</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
} else {
|
|
return (
|
|
<Input
|
|
type={inputType}
|
|
placeholder={inputType === "number" ? "숫자" : "값"}
|
|
value={String(condition.value || "")}
|
|
onChange={(e) => onUpdateCondition(index, "value", e.target.value)}
|
|
className="h-8 flex-1 text-xs"
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{conditions.length === 0 ? (
|
|
<div className="rounded-lg border border-dashed p-3 text-center text-xs text-gray-500">
|
|
조건을 추가하면 해당 조건을 만족할 때만 실행됩니다.
|
|
<br />
|
|
조건이 없으면 항상 실행됩니다.
|
|
</div>
|
|
) : (
|
|
<React.Fragment key="conditions-list">
|
|
{conditions.map((condition, index) => {
|
|
// 그룹 시작 렌더링
|
|
if (condition.type === "group-start") {
|
|
return (
|
|
<div key={condition.id} className="flex items-center gap-2">
|
|
{/* 그룹 시작 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */}
|
|
{index > 0 && conditions[index - 1]?.type !== "group-end" && (
|
|
<Select
|
|
value={condition.logicalOperator || "AND"}
|
|
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)}
|
|
>
|
|
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="AND">AND</SelectItem>
|
|
<SelectItem value="OR">OR</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
{/* 그룹 레벨에 따른 들여쓰기 */}
|
|
<div
|
|
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-blue-50/50 p-2"
|
|
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
|
|
>
|
|
<span className="font-mono text-sm text-blue-600">(</span>
|
|
<span className="text-xs text-blue-600">그룹 시작</span>
|
|
<Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0">
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 그룹 끝 렌더링
|
|
if (condition.type === "group-end") {
|
|
return (
|
|
<div key={condition.id} className="flex items-center gap-2">
|
|
<div
|
|
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-blue-50/50 p-2"
|
|
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
|
|
>
|
|
<span className="font-mono text-sm text-blue-600">)</span>
|
|
<span className="text-xs text-blue-600">그룹 끝</span>
|
|
<Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0">
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 그룹 끝 다음에 다른 조건이나 그룹이 있으면 논리 연산자 표시 */}
|
|
{index < conditions.length - 1 && (
|
|
<Select
|
|
value={conditions[index + 1]?.logicalOperator || "AND"}
|
|
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index + 1, "logicalOperator", value)}
|
|
>
|
|
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="AND">AND</SelectItem>
|
|
<SelectItem value="OR">OR</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 일반 조건 렌더링
|
|
return (
|
|
<div key={condition.id} className="flex items-center gap-2">
|
|
{/* 일반 조건 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */}
|
|
{index > 0 &&
|
|
conditions[index - 1]?.type !== "group-start" &&
|
|
conditions[index - 1]?.type !== "group-end" && (
|
|
<Select
|
|
value={condition.logicalOperator || "AND"}
|
|
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)}
|
|
>
|
|
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="AND">AND</SelectItem>
|
|
<SelectItem value="OR">OR</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
|
|
{/* 그룹 레벨에 따른 들여쓰기와 조건 필드들 */}
|
|
<div
|
|
className="flex flex-1 items-center gap-2 rounded border bg-white p-2"
|
|
style={{ marginLeft: `${getCurrentGroupLevel(index) * 20}px` }}
|
|
>
|
|
{/* 조건 필드 선택 */}
|
|
<Select
|
|
value={condition.field || ""}
|
|
onValueChange={(value) => onUpdateCondition(index, "field", value)}
|
|
>
|
|
<SelectTrigger className="h-8 flex-1 text-xs">
|
|
<SelectValue placeholder="필드 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{fromTableColumns.map((column) => (
|
|
<SelectItem key={column.columnName} value={column.columnName}>
|
|
{column.displayName || column.columnLabel || column.columnName}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
{/* 연산자 선택 */}
|
|
<Select
|
|
value={condition.operator || "="}
|
|
onValueChange={(value) => onUpdateCondition(index, "operator", value)}
|
|
>
|
|
<SelectTrigger className="h-8 w-20 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="=">=</SelectItem>
|
|
<SelectItem value="!=">!=</SelectItem>
|
|
<SelectItem value=">">></SelectItem>
|
|
<SelectItem value="<"><</SelectItem>
|
|
<SelectItem value=">=">>=</SelectItem>
|
|
<SelectItem value="<="><=</SelectItem>
|
|
<SelectItem value="LIKE">LIKE</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
|
|
{/* 값 입력 */}
|
|
{renderConditionValue(condition, index)}
|
|
|
|
{/* 삭제 버튼 */}
|
|
<Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-8 w-8 p-0">
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</React.Fragment>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|