ConnectionSetupModal.tsx UI 재설계

This commit is contained in:
hyeonsu 2025-09-12 09:58:49 +09:00
parent 978a4937ad
commit 441a5712c1
2 changed files with 201 additions and 16 deletions

View File

@ -8,8 +8,8 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import { ArrowRight, Link, Key, Save, Globe, Plus } from "lucide-react";
import { DataFlowAPI, TableRelationship, TableInfo, ColumnInfo } from "@/lib/api/dataflow";
import { Link, Key, Save, Globe, Plus, Zap, Trash2 } from "lucide-react";
import { DataFlowAPI, TableRelationship, TableInfo, ColumnInfo, ConditionNode } from "@/lib/api/dataflow";
import toast from "react-hot-toast";
// 연결 정보 타입
@ -36,7 +36,7 @@ interface ConnectionInfo {
relationshipName: string;
relationshipType: string;
connectionType: string;
settings?: any;
settings?: Record<string, unknown>;
};
}
@ -76,7 +76,6 @@ interface ConnectionSetupModalProps {
isOpen: boolean;
connection: ConnectionInfo | null;
companyCode: string;
diagramId?: number;
onConfirm: (relationship: TableRelationship) => void;
onCancel: () => void;
}
@ -85,7 +84,6 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
isOpen,
connection,
companyCode,
diagramId,
onConfirm,
onCancel,
}) => {
@ -127,6 +125,12 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
const [selectedFromColumns, setSelectedFromColumns] = useState<string[]>([]);
const [selectedToColumns, setSelectedToColumns] = useState<string[]>([]);
// 조건부 연결을 위한 새로운 상태들
const [triggerType, setTriggerType] = useState<"insert" | "update" | "delete" | "insert_update">("insert");
const [conditions, setConditions] = useState<ConditionNode[]>([]);
const [rollbackOnError, setRollbackOnError] = useState(true);
const [enableLogging, setEnableLogging] = useState(true);
// 테이블 목록 로드
useEffect(() => {
const loadTables = async () => {
@ -166,7 +170,8 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
connectionType: (existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key",
fromColumnName: "",
toColumnName: "",
description: existingRel?.settings?.description || `${fromDisplayName}${toDisplayName} 간의 데이터 관계`,
description:
(existingRel?.settings?.description as string) || `${fromDisplayName}${toDisplayName} 간의 데이터 관계`,
settings: existingRel?.settings || {},
});
@ -282,6 +287,32 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
const fromTableName = selectedFromTable || connection.fromNode.tableName;
const toTableName = selectedToTable || connection.toNode.tableName;
// 조건부 연결 설정 데이터 준비
const conditionalSettings = isConditionalConnection()
? {
control: {
triggerType,
conditionTree:
conditions.length > 0
? {
type: "group" as const,
operator: "AND" as const,
children: conditions,
}
: null,
},
category: {
type: config.connectionType,
rollbackOnError,
enableLogging,
},
plan: {
sourceTable: fromTableName,
targetActions: [], // 나중에 액션 설정 UI에서 채울 예정
},
}
: {};
// 메모리 기반 시스템: 관계 데이터만 생성하여 부모로 전달
const relationshipData: TableRelationship = {
relationship_name: config.relationshipName,
@ -289,11 +320,12 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
to_table_name: toTableName,
from_column_name: selectedFromColumns.join(","), // 여러 컬럼을 콤마로 구분
to_column_name: selectedToColumns.join(","), // 여러 컬럼을 콤마로 구분
relationship_type: config.relationshipType as any,
connection_type: config.connectionType as any,
relationship_type: config.relationshipType,
connection_type: config.connectionType,
company_code: companyCode,
settings: {
...settings,
...conditionalSettings, // 조건부 연결 설정 추가
description: config.description,
multiColumnMapping: {
fromColumns: selectedFromColumns,
@ -330,14 +362,165 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
if (!connection) return null;
// 선택된 컬럼 데이터 가져오기
const selectedColumnsData = connection.selectedColumnsData || {};
const tableNames = Object.keys(selectedColumnsData);
const fromTable = tableNames[0];
const toTable = tableNames[1];
// 선택된 컬럼 데이터 가져오기 (현재 사용되지 않음 - 향후 확장을 위해 유지)
// const selectedColumnsData = connection.selectedColumnsData || {};
const fromTableData = selectedColumnsData[fromTable];
const toTableData = selectedColumnsData[toTable];
// 조건부 연결인지 확인하는 헬퍼 함수
const isConditionalConnection = () => {
return config.connectionType === "data-save" || config.connectionType === "external-call";
};
// 조건 관리 헬퍼 함수들
const addCondition = () => {
const newCondition: ConditionNode = {
type: "condition",
field: "",
operator_type: "=",
value: "",
dataType: "string",
};
setConditions([...conditions, newCondition]);
};
const updateCondition = (index: number, field: keyof ConditionNode, value: string) => {
const updatedConditions = [...conditions];
updatedConditions[index] = { ...updatedConditions[index], [field]: value };
setConditions(updatedConditions);
};
const removeCondition = (index: number) => {
const updatedConditions = conditions.filter((_, i) => i !== index);
setConditions(updatedConditions);
};
// 조건부 연결 설정 UI 렌더링
const renderConditionalSettings = () => {
return (
<div className="rounded-lg border border-l-4 border-l-purple-500 bg-purple-50/30 p-4">
<div className="mb-4 flex items-center gap-2">
<Zap className="h-4 w-4 text-purple-500" />
<span className="text-sm font-medium"> </span>
</div>
{/* 트리거 타입 선택 */}
<div className="mb-4">
<Label htmlFor="triggerType" className="mb-2 block text-sm font-medium">
</Label>
<Select
value={triggerType}
onValueChange={(value: "insert" | "update" | "delete" | "insert_update") => setTriggerType(value)}
>
<SelectTrigger className="text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="insert"> (INSERT)</SelectItem>
<SelectItem value="update"> (UPDATE)</SelectItem>
<SelectItem value="delete"> (DELETE)</SelectItem>
<SelectItem value="insert_update">/ (INSERT/UPDATE)</SelectItem>
</SelectContent>
</Select>
</div>
{/* 실행 조건 설정 */}
<div className="mb-4">
<div className="mb-2 flex items-center justify-between">
<Label className="text-sm font-medium"> </Label>
<Button size="sm" variant="outline" onClick={() => addCondition()} className="h-7 text-xs">
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
{/* 조건 목록 */}
<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>
) : (
conditions.map((condition, index) => (
<div key={index} className="flex items-center gap-2 rounded border bg-white p-2">
<Select
value={condition.field || ""}
onValueChange={(value) => updateCondition(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.columnName} ({column.dataType})
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={condition.operator_type || "="}
onValueChange={(value) => updateCondition(index, "operator_type", value)}
>
<SelectTrigger className="h-8 w-20 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="=">=</SelectItem>
<SelectItem value="!=">!=</SelectItem>
<SelectItem value=">">&gt;</SelectItem>
<SelectItem value="<">&lt;</SelectItem>
<SelectItem value=">=">&gt;=</SelectItem>
<SelectItem value="<=">&lt;=</SelectItem>
<SelectItem value="LIKE">LIKE</SelectItem>
<SelectItem value="IN">IN</SelectItem>
<SelectItem value="IS_NULL">IS NULL</SelectItem>
</SelectContent>
</Select>
<Input
placeholder="값"
value={condition.value || ""}
onChange={(e) => updateCondition(index, "value", e.target.value)}
className="h-8 flex-1 text-xs"
/>
<Button size="sm" variant="ghost" onClick={() => removeCondition(index)} className="h-8 w-8 p-0">
<Trash2 className="h-3 w-3" />
</Button>
</div>
))
)}
</div>
</div>
{/* 추가 옵션 */}
<div className="grid grid-cols-2 gap-4 border-t pt-2">
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={rollbackOnError}
onChange={(e) => setRollbackOnError(e.target.checked)}
className="rounded"
/>
</label>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={enableLogging}
onChange={(e) => setEnableLogging(e.target.checked)}
className="rounded"
/>
</label>
</div>
</div>
);
};
// 연결 종류별 설정 패널 렌더링
const renderConnectionTypeSettings = () => {
@ -724,6 +907,9 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
</div>
</div>
{/* 조건부 연결을 위한 조건 설정 */}
{isConditionalConnection() && renderConditionalSettings()}
{/* 연결 종류별 상세 설정 */}
{renderConnectionTypeSettings()}
</div>

View File

@ -1254,7 +1254,6 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
isOpen={!!pendingConnection}
connection={pendingConnection}
companyCode={companyCode}
diagramId={currentDiagramId || diagramId || (relationshipId ? parseInt(relationshipId) : undefined)}
onConfirm={handleConfirmConnection}
onCancel={handleCancelConnection}
/>