ERP-node/frontend/components/screen/ScreenRelationView.tsx

279 lines
10 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { ScreenDefinition } from "@/types/screen";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Database, Monitor, ArrowRight, Link2, Table, Columns, ExternalLink, Layers, GitBranch } from "lucide-react";
import {
getFieldJoins,
getDataFlows,
getTableRelations,
FieldJoin,
DataFlow,
TableRelation,
} from "@/lib/api/screenGroup";
import { screenApi } from "@/lib/api/screen";
interface ScreenRelationViewProps {
screen: ScreenDefinition | null;
}
export function ScreenRelationView({ screen }: ScreenRelationViewProps) {
const [loading, setLoading] = useState(false);
const [fieldJoins, setFieldJoins] = useState<FieldJoin[]>([]);
const [dataFlows, setDataFlows] = useState<DataFlow[]>([]);
const [tableRelations, setTableRelations] = useState<TableRelation[]>([]);
const [layoutInfo, setLayoutInfo] = useState<any>(null);
useEffect(() => {
const loadRelations = async () => {
if (!screen) {
setFieldJoins([]);
setDataFlows([]);
setTableRelations([]);
setLayoutInfo(null);
return;
}
try {
setLoading(true);
// 병렬로 데이터 로드
const [joinsRes, flowsRes, relationsRes, layoutRes] = await Promise.all([
getFieldJoins(screen.screenId),
getDataFlows(screen.screenId),
getTableRelations(screen.screenId),
screenApi.getLayout(screen.screenId).catch(() => null),
]);
if (joinsRes.success && joinsRes.data) {
setFieldJoins(joinsRes.data);
}
if (flowsRes.success && flowsRes.data) {
setDataFlows(flowsRes.data);
}
if (relationsRes.success && relationsRes.data) {
setTableRelations(relationsRes.data);
}
if (layoutRes) {
setLayoutInfo(layoutRes);
}
} catch (error) {
console.error("관계 정보 로드 실패:", error);
} finally {
setLoading(false);
}
};
loadRelations();
}, [screen?.screenId]);
if (!screen) {
return (
<div className="flex h-full flex-col items-center justify-center py-12 text-center">
<Layers className="text-muted-foreground/30 mb-4 h-16 w-16" />
<h3 className="text-muted-foreground mb-2 text-lg font-medium"> </h3>
<p className="text-muted-foreground/70 text-sm"> </p>
</div>
);
}
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="text-muted-foreground text-sm"> ...</div>
</div>
);
}
// 컴포넌트에서 사용하는 테이블 분석
const getUsedTables = () => {
const tables = new Set<string>();
if (screen.tableName) {
tables.add(screen.tableName);
}
if (layoutInfo?.components) {
layoutInfo.components.forEach((comp: any) => {
if (comp.properties?.tableName) {
tables.add(comp.properties.tableName);
}
if (comp.properties?.dataSource?.tableName) {
tables.add(comp.properties.dataSource.tableName);
}
});
}
return Array.from(tables);
};
const usedTables = getUsedTables();
return (
<div className="h-full space-y-4 overflow-auto p-4">
{/* 화면 기본 정보 */}
<div className="flex items-start gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-blue-500/10">
<Monitor className="h-6 w-6 text-blue-500" />
</div>
<div className="min-w-0 flex-1">
<h3 className="truncate text-lg font-semibold">{screen.screenName}</h3>
<div className="mt-1 flex items-center gap-2">
<Badge variant="outline">{screen.screenCode}</Badge>
<Badge variant="secondary">{screen.screenType}</Badge>
</div>
{screen.description && (
<p className="text-muted-foreground mt-2 line-clamp-2 text-sm">{screen.description}</p>
)}
</div>
</div>
<Separator />
{/* 연결된 테이블 */}
<Card>
<CardHeader className="px-4 py-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Database className="h-4 w-4 text-green-500" />
</CardTitle>
</CardHeader>
<CardContent className="px-4 pt-0 pb-4">
{usedTables.length > 0 ? (
<div className="space-y-2">
{usedTables.map((tableName, index) => (
<div key={index} className="bg-muted/50 flex items-center gap-2 rounded-md p-2">
<Table className="text-muted-foreground h-4 w-4" />
<span className="font-mono text-sm">{tableName}</span>
{tableName === screen.tableName && (
<Badge variant="default" className="ml-auto text-xs">
</Badge>
)}
</div>
))}
</div>
) : (
<p className="text-muted-foreground text-sm"> </p>
)}
</CardContent>
</Card>
{/* 필드 조인 관계 */}
<Card>
<CardHeader className="px-4 py-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Link2 className="h-4 w-4 text-purple-500" />
{fieldJoins.length > 0 && (
<Badge variant="secondary" className="ml-auto">
{fieldJoins.length}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="px-4 pt-0 pb-4">
{fieldJoins.length > 0 ? (
<div className="space-y-2">
{fieldJoins.map((join) => (
<div key={join.id} className="bg-muted/50 flex items-center gap-2 rounded-md p-2 text-sm">
<div className="flex items-center gap-1">
<span className="font-mono text-xs">{join.sourceTable}</span>
<span className="text-muted-foreground">.</span>
<span className="font-mono text-xs text-blue-600">{join.sourceColumn}</span>
</div>
<ArrowRight className="text-muted-foreground h-3 w-3" />
<div className="flex items-center gap-1">
<span className="font-mono text-xs">{join.targetTable}</span>
<span className="text-muted-foreground">.</span>
<span className="font-mono text-xs text-green-600">{join.targetColumn}</span>
</div>
<Badge variant="outline" className="ml-auto text-xs">
{join.joinType}
</Badge>
</div>
))}
</div>
) : (
<p className="text-muted-foreground text-sm"> </p>
)}
</CardContent>
</Card>
{/* 데이터 흐름 */}
<Card>
<CardHeader className="px-4 py-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<GitBranch className="h-4 w-4 text-orange-500" />
{dataFlows.length > 0 && (
<Badge variant="secondary" className="ml-auto">
{dataFlows.length}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="px-4 pt-0 pb-4">
{dataFlows.length > 0 ? (
<div className="space-y-2">
{dataFlows.map((flow) => (
<div key={flow.id} className="bg-muted/50 flex items-center gap-2 rounded-md p-2 text-sm">
<Monitor className="h-4 w-4 text-blue-500" />
<span className="truncate">{flow.flowName || "이름 없음"}</span>
<ArrowRight className="text-muted-foreground h-3 w-3" />
<Monitor className="h-4 w-4 text-green-500" />
<span className="text-muted-foreground truncate"> #{flow.targetScreenId}</span>
<Badge variant="outline" className="ml-auto text-xs">
{flow.flowType}
</Badge>
</div>
))}
</div>
) : (
<p className="text-muted-foreground text-sm"> </p>
)}
</CardContent>
</Card>
{/* 테이블 관계 */}
<Card>
<CardHeader className="px-4 py-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Columns className="h-4 w-4 text-cyan-500" />
{tableRelations.length > 0 && (
<Badge variant="secondary" className="ml-auto">
{tableRelations.length}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="px-4 pt-0 pb-4">
{tableRelations.length > 0 ? (
<div className="space-y-2">
{tableRelations.map((relation) => (
<div key={relation.id} className="bg-muted/50 flex items-center gap-2 rounded-md p-2 text-sm">
<Table className="text-muted-foreground h-4 w-4" />
<span className="font-mono text-xs">{relation.parentTable}</span>
<ArrowRight className="text-muted-foreground h-3 w-3" />
<span className="font-mono text-xs">{relation.childTable}</span>
<Badge variant="outline" className="ml-auto text-xs">
{relation.relationType}
</Badge>
</div>
))}
</div>
) : (
<p className="text-muted-foreground text-sm"> </p>
)}
</CardContent>
</Card>
{/* 빠른 작업 */}
<div className="border-t pt-2">
<p className="text-muted-foreground mb-2 text-xs"> </p>
</div>
</div>
);
}