ERP-node/frontend/components/dataflow/ConnectionSetupModal.tsx

321 lines
12 KiB
TypeScript
Raw Normal View History

2025-09-05 16:19:31 +09:00
"use client";
import React, { useState, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import { ArrowRight, Database, Link } from "lucide-react";
// 연결 정보 타입
interface ConnectionInfo {
fromNode: {
id: string;
screenName: string;
tableName: string;
};
toNode: {
id: string;
screenName: string;
tableName: string;
};
fromField?: string;
toField?: string;
selectedFieldsData?: {
[screenId: string]: {
screenName: string;
fields: string[];
};
};
orderedScreenIds?: string[]; // 선택 순서 정보
}
// 연결 설정 타입
interface ConnectionConfig {
relationshipName: string;
relationshipType: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many";
connectionType: "simple-key" | "data-save" | "external-call";
fromFieldName: string;
toFieldName: string;
settings?: Record<string, any>;
description?: string;
}
interface ConnectionSetupModalProps {
isOpen: boolean;
connection: ConnectionInfo | null;
onConfirm: (config: ConnectionConfig) => void;
onCancel: () => void;
}
export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
isOpen,
connection,
onConfirm,
onCancel,
}) => {
const [relationshipName, setRelationshipName] = useState("");
const [relationshipType, setRelationshipType] = useState<ConnectionConfig["relationshipType"]>("one-to-one");
const [connectionType, setConnectionType] = useState<ConnectionConfig["connectionType"]>("simple-key");
const [fromFieldName, setFromFieldName] = useState("");
const [toFieldName, setToFieldName] = useState("");
const [description, setDescription] = useState("");
// 모달이 열릴 때마다 초기화
useEffect(() => {
if (isOpen && connection) {
// 기본 관계명 생성
const defaultName = `${connection.fromNode.screenName}_${connection.toNode.screenName}`;
setRelationshipName(defaultName);
setRelationshipType("one-to-one");
setConnectionType("simple-key");
// 시작/대상 필드는 비워둠 (다음 기능에서 사용)
setFromFieldName("");
setToFieldName("");
setDescription("");
}
}, [isOpen, connection]);
const handleConfirm = () => {
if (!relationshipName.trim()) {
alert("관계명을 입력해주세요.");
return;
}
const config: ConnectionConfig = {
relationshipName: relationshipName.trim(),
relationshipType,
connectionType,
fromFieldName,
toFieldName,
description: description.trim() || undefined,
};
onConfirm(config);
};
const getRelationshipTypeDescription = (type: string) => {
switch (type) {
case "one-to-one":
return "1:1 - 한 레코드가 다른 테이블의 한 레코드와 연결";
case "one-to-many":
return "1:N - 한 레코드가 다른 테이블의 여러 레코드와 연결";
case "many-to-one":
return "N:1 - 여러 레코드가 다른 테이블의 한 레코드와 연결";
case "many-to-many":
return "N:N - 여러 레코드가 다른 테이블의 여러 레코드와 연결 (중계 테이블 생성)";
default:
return "";
}
};
const getConnectionTypeDescription = (type: string) => {
switch (type) {
case "simple-key":
return "단순 키값 연결 - 기본 참조 관계";
case "data-save":
return "데이터 저장 - 필드 매핑을 통한 데이터 저장";
case "external-call":
return "외부 호출 - API, 이메일, 웹훅 등을 통한 외부 시스템 연동";
default:
return "";
}
};
if (!connection) return null;
return (
<Dialog open={isOpen} onOpenChange={onCancel}>
<DialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Link className="h-5 w-5" />
</DialogTitle>
</DialogHeader>
<div className="space-y-6">
{/* 연결 정보 표시 */}
<Card>
<CardHeader>
<CardTitle className="text-sm"> </CardTitle>
</CardHeader>
<CardContent>
{connection.selectedFieldsData && connection.orderedScreenIds ? (
<div className="flex items-center gap-4">
{/* orderedScreenIds 순서대로 표시 */}
{connection.orderedScreenIds.map((screenId, index) => {
const screenData = connection.selectedFieldsData[screenId];
if (!screenData) return null;
return (
<React.Fragment key={screenId}>
<div className="flex-1">
<div className="mb-2 flex flex-wrap items-center gap-2">
<div className="flex-shrink-0 rounded bg-blue-600 px-2 py-1 text-xs font-medium text-white">
{screenData.screenName}
</div>
<div className="flex-shrink-0 text-xs text-gray-500">ID: {screenId}</div>
<div className="flex flex-shrink-0 items-center gap-1 text-xs text-gray-500">
<Database className="h-3 w-3" />
{index === 0 ? connection.fromNode.tableName : connection.toNode.tableName}
</div>
</div>
<div className="flex flex-wrap gap-1">
{screenData.fields.map((field) => (
<Badge key={field} variant="outline" className="text-xs">
{field}
</Badge>
))}
</div>
</div>
{/* 첫 번째 화면 다음에 화살표 표시 */}
{index === 0 && connection.orderedScreenIds.length > 1 && (
<div className="flex items-center justify-center">
<ArrowRight className="h-5 w-5 text-gray-400" />
</div>
)}
</React.Fragment>
);
})}
</div>
) : (
<div className="flex items-center gap-4">
<div className="flex-1">
<div className="text-sm font-medium">{connection.fromNode.screenName}</div>
<div className="flex items-center gap-1 text-xs text-gray-500">
<Database className="h-3 w-3" />
{connection.fromNode.tableName}
</div>
</div>
<ArrowRight className="h-4 w-4 text-gray-400" />
<div className="flex-1">
<div className="text-sm font-medium">{connection.toNode.screenName}</div>
<div className="flex items-center gap-1 text-xs text-gray-500">
<Database className="h-3 w-3" />
{connection.toNode.tableName}
</div>
</div>
</div>
)}
</CardContent>
</Card>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* 기본 설정 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold"> </h3>
<div className="space-y-2">
<Label htmlFor="relationshipName"> *</Label>
<Input
id="relationshipName"
value={relationshipName}
onChange={(e) => setRelationshipName(e.target.value)}
placeholder="관계를 설명하는 이름을 입력하세요"
/>
</div>
<div className="space-y-2">
<Label htmlFor="fromField"> *</Label>
<Input
id="fromField"
value={fromFieldName}
onChange={(e) => setFromFieldName(e.target.value)}
placeholder="시작 테이블의 필드명"
/>
</div>
<div className="space-y-2">
<Label htmlFor="toField"> *</Label>
<Input
id="toField"
value={toFieldName}
onChange={(e) => setToFieldName(e.target.value)}
placeholder="대상 테이블의 필드명"
/>
</div>
<div className="space-y-2">
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="관계에 대한 설명을 입력하세요"
rows={3}
/>
</div>
</div>
{/* 관계 설정 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold"> </h3>
<div className="space-y-2">
<Label> </Label>
<Select value={relationshipType} onValueChange={(value: any) => setRelationshipType(value)}>
<SelectTrigger>
<SelectValue placeholder="관계 타입을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="one-to-one">1:1 (One to One)</SelectItem>
<SelectItem value="one-to-many">1:N (One to Many)</SelectItem>
<SelectItem value="many-to-one">N:1 (Many to One)</SelectItem>
<SelectItem value="many-to-many">N:N (Many to Many)</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-600">{getRelationshipTypeDescription(relationshipType)}</p>
</div>
<div className="space-y-2">
<Label> </Label>
<Select value={connectionType} onValueChange={(value: any) => setConnectionType(value)}>
<SelectTrigger>
<SelectValue placeholder="연결 종류를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="simple-key"> </SelectItem>
<SelectItem value="data-save"> </SelectItem>
<SelectItem value="external-call"> </SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-600">{getConnectionTypeDescription(connectionType)}</p>
</div>
{/* N:N 관계일 때 추가 정보 */}
{relationshipType === "many-to-many" && (
<Card className="border-yellow-200 bg-yellow-50">
<CardContent className="pt-4">
<div className="text-sm text-yellow-800">
<strong>N:N :</strong>
<ul className="mt-2 space-y-1 text-xs">
<li> </li>
<li>
: {connection.fromNode.tableName}_{connection.toNode.tableName}
</li>
<li> </li>
</ul>
</div>
</CardContent>
</Card>
)}
</div>
</div>
</div>
<DialogFooter>
<Button onClick={onCancel} variant="outline">
</Button>
<Button onClick={handleConfirm}> </Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};