테이블 기반 방식으로 변경
This commit is contained in:
@@ -15,23 +15,22 @@ import { ArrowRight, Database, Link } from "lucide-react";
|
||||
interface ConnectionInfo {
|
||||
fromNode: {
|
||||
id: string;
|
||||
screenName: string;
|
||||
tableName: string;
|
||||
displayName: string;
|
||||
};
|
||||
toNode: {
|
||||
id: string;
|
||||
screenName: string;
|
||||
tableName: string;
|
||||
displayName: string;
|
||||
};
|
||||
fromField?: string;
|
||||
toField?: string;
|
||||
selectedFieldsData?: {
|
||||
[screenId: string]: {
|
||||
screenName: string;
|
||||
fields: string[];
|
||||
fromColumn?: string;
|
||||
toColumn?: string;
|
||||
selectedColumnsData?: {
|
||||
[tableName: string]: {
|
||||
displayName: string;
|
||||
columns: string[];
|
||||
};
|
||||
};
|
||||
orderedScreenIds?: string[]; // 선택 순서 정보
|
||||
}
|
||||
|
||||
// 연결 설정 타입
|
||||
@@ -39,8 +38,8 @@ 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;
|
||||
fromColumnName: string;
|
||||
toColumnName: string;
|
||||
settings?: Record<string, any>;
|
||||
description?: string;
|
||||
}
|
||||
@@ -58,261 +57,221 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
||||
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("");
|
||||
const [config, setConfig] = useState<ConnectionConfig>({
|
||||
relationshipName: "",
|
||||
relationshipType: "one-to-one",
|
||||
connectionType: "simple-key",
|
||||
fromColumnName: "",
|
||||
toColumnName: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
// 모달이 열릴 때마다 초기화
|
||||
// 모달이 열릴 때 기본값 설정
|
||||
useEffect(() => {
|
||||
if (isOpen && connection) {
|
||||
// 기본 관계명 생성
|
||||
const defaultName = `${connection.fromNode.screenName}_${connection.toNode.screenName}`;
|
||||
setRelationshipName(defaultName);
|
||||
setRelationshipType("one-to-one");
|
||||
setConnectionType("simple-key");
|
||||
// 시작/대상 필드는 비워둠 (다음 기능에서 사용)
|
||||
setFromFieldName("");
|
||||
setToFieldName("");
|
||||
setDescription("");
|
||||
const fromTableName = connection.fromNode.displayName;
|
||||
const toTableName = connection.toNode.displayName;
|
||||
|
||||
setConfig({
|
||||
relationshipName: `${fromTableName} → ${toTableName}`,
|
||||
relationshipType: "one-to-one",
|
||||
connectionType: "simple-key",
|
||||
fromColumnName: "",
|
||||
toColumnName: "",
|
||||
description: `${fromTableName}과 ${toTableName} 간의 데이터 관계`,
|
||||
});
|
||||
}
|
||||
}, [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 "";
|
||||
if (config.relationshipName && config.fromColumnName && config.toColumnName) {
|
||||
onConfirm(config);
|
||||
handleCancel(); // 모달 닫기
|
||||
}
|
||||
};
|
||||
|
||||
const getConnectionTypeDescription = (type: string) => {
|
||||
switch (type) {
|
||||
case "simple-key":
|
||||
return "단순 키값 연결 - 기본 참조 관계";
|
||||
case "data-save":
|
||||
return "데이터 저장 - 필드 매핑을 통한 데이터 저장";
|
||||
case "external-call":
|
||||
return "외부 호출 - API, 이메일, 웹훅 등을 통한 외부 시스템 연동";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
const handleCancel = () => {
|
||||
setConfig({
|
||||
relationshipName: "",
|
||||
relationshipType: "one-to-one",
|
||||
connectionType: "simple-key",
|
||||
fromColumnName: "",
|
||||
toColumnName: "",
|
||||
description: "",
|
||||
});
|
||||
onCancel();
|
||||
};
|
||||
|
||||
if (!connection) return null;
|
||||
|
||||
// 선택된 컬럼 데이터 가져오기
|
||||
const selectedColumnsData = connection.selectedColumnsData || {};
|
||||
const tableNames = Object.keys(selectedColumnsData);
|
||||
const fromTable = tableNames[0];
|
||||
const toTable = tableNames[1];
|
||||
|
||||
const fromTableData = selectedColumnsData[fromTable];
|
||||
const toTableData = selectedColumnsData[toTable];
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onCancel}>
|
||||
<Dialog open={isOpen} onOpenChange={handleCancel}>
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<DialogTitle className="flex items-center gap-2 text-xl">
|
||||
<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 className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{/* 시작 테이블 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">
|
||||
<Database className="h-4 w-4" />
|
||||
시작 테이블
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium">{fromTableData?.displayName || fromTable}</div>
|
||||
<div className="text-xs text-gray-500">{fromTable}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{fromTableData?.columns.map((column, index) => (
|
||||
<Badge key={`${fromTable}-${column}-${index}`} variant="outline" className="text-xs">
|
||||
{column}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</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 className="flex items-center justify-center md:hidden">
|
||||
<ArrowRight className="h-6 w-6 text-gray-400" />
|
||||
</div>
|
||||
|
||||
{/* 관계 설정 */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">관계 설정</h3>
|
||||
{/* 대상 테이블 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">
|
||||
<Database className="h-4 w-4" />
|
||||
대상 테이블
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium">{toTableData?.displayName || toTable}</div>
|
||||
<div className="text-xs text-gray-500">{toTable}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{toTableData?.columns.map((column, index) => (
|
||||
<Badge key={`${toTable}-${column}-${index}`} variant="outline" className="text-xs">
|
||||
{column}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<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 className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="relationshipName">관계명</Label>
|
||||
<Input
|
||||
id="relationshipName"
|
||||
value={config.relationshipName}
|
||||
onChange={(e) => setConfig({ ...config, relationshipName: e.target.value })}
|
||||
placeholder="관계명을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>연결 종류</Label>
|
||||
<Select value={connectionType} onValueChange={(value: any) => setConnectionType(value)}>
|
||||
<div>
|
||||
<Label htmlFor="relationshipType">관계 유형</Label>
|
||||
<Select
|
||||
value={config.relationshipType}
|
||||
onValueChange={(value: any) => setConfig({ ...config, relationshipType: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="연결 종류를 선택하세요" />
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="simple-key">단순 키값 연결</SelectItem>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="connectionType">연결 방식</Label>
|
||||
<Select
|
||||
value={config.connectionType}
|
||||
onValueChange={(value: any) => setConfig({ ...config, connectionType: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="fromColumnName">시작 컬럼</Label>
|
||||
<Input
|
||||
id="fromColumnName"
|
||||
value={config.fromColumnName}
|
||||
onChange={(e) => setConfig({ ...config, fromColumnName: e.target.value })}
|
||||
placeholder="시작 컬럼명을 입력하세요"
|
||||
/>
|
||||
</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>
|
||||
<Label htmlFor="toColumnName">대상 컬럼</Label>
|
||||
<Input
|
||||
id="toColumnName"
|
||||
value={config.toColumnName}
|
||||
onChange={(e) => setConfig({ ...config, toColumnName: e.target.value })}
|
||||
placeholder="대상 컬럼명을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="description">설명</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={config.description}
|
||||
onChange={(e) => setConfig({ ...config, description: e.target.value })}
|
||||
placeholder="연결에 대한 설명을 입력하세요"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={onCancel} variant="outline">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleConfirm}>연결 생성</Button>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={!config.relationshipName || !config.fromColumnName || !config.toColumnName}
|
||||
>
|
||||
연결 생성
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user