297 lines
10 KiB
TypeScript
297 lines
10 KiB
TypeScript
"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 flex-col items-center justify-center h-full text-center py-12">
|
|
<Layers className="h-16 w-16 text-muted-foreground/30 mb-4" />
|
|
<h3 className="text-lg font-medium text-muted-foreground mb-2">화면을 선택하세요</h3>
|
|
<p className="text-sm text-muted-foreground/70">
|
|
왼쪽 트리에서 화면을 선택하면 데이터 관계가 표시됩니다
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-8">
|
|
<div className="text-sm text-muted-foreground">로딩 중...</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="p-4 space-y-4 overflow-auto h-full">
|
|
{/* 화면 기본 정보 */}
|
|
<div className="flex items-start gap-4">
|
|
<div className="flex items-center justify-center w-12 h-12 rounded-lg bg-blue-500/10">
|
|
<Monitor className="h-6 w-6 text-blue-500" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="font-semibold text-lg truncate">{screen.screenName}</h3>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<Badge variant="outline">{screen.screenCode}</Badge>
|
|
<Badge variant="secondary">{screen.screenType}</Badge>
|
|
</div>
|
|
{screen.description && (
|
|
<p className="text-sm text-muted-foreground mt-2 line-clamp-2">
|
|
{screen.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* 연결된 테이블 */}
|
|
<Card>
|
|
<CardHeader className="py-3 px-4">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<Database className="h-4 w-4 text-green-500" />
|
|
연결된 테이블
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="px-4 pb-4 pt-0">
|
|
{usedTables.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{usedTables.map((tableName, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center gap-2 p-2 rounded-md bg-muted/50"
|
|
>
|
|
<Table className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-sm font-mono">{tableName}</span>
|
|
{tableName === screen.tableName && (
|
|
<Badge variant="default" className="text-xs ml-auto">
|
|
메인
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground">연결된 테이블이 없습니다</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 필드 조인 관계 */}
|
|
<Card>
|
|
<CardHeader className="py-3 px-4">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<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 pb-4 pt-0">
|
|
{fieldJoins.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{fieldJoins.map((join) => (
|
|
<div
|
|
key={join.id}
|
|
className="flex items-center gap-2 p-2 rounded-md bg-muted/50 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="h-3 w-3 text-muted-foreground" />
|
|
<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-sm text-muted-foreground">설정된 조인이 없습니다</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 데이터 흐름 */}
|
|
<Card>
|
|
<CardHeader className="py-3 px-4">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<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 pb-4 pt-0">
|
|
{dataFlows.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{dataFlows.map((flow) => (
|
|
<div
|
|
key={flow.id}
|
|
className="flex items-center gap-2 p-2 rounded-md bg-muted/50 text-sm"
|
|
>
|
|
<Monitor className="h-4 w-4 text-blue-500" />
|
|
<span className="truncate">{flow.flowName || "이름 없음"}</span>
|
|
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
|
<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-sm text-muted-foreground">설정된 데이터 흐름이 없습니다</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 테이블 관계 */}
|
|
<Card>
|
|
<CardHeader className="py-3 px-4">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<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 pb-4 pt-0">
|
|
{tableRelations.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{tableRelations.map((relation) => (
|
|
<div
|
|
key={relation.id}
|
|
className="flex items-center gap-2 p-2 rounded-md bg-muted/50 text-sm"
|
|
>
|
|
<Table className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-mono text-xs">{relation.parentTable}</span>
|
|
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
|
<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-sm text-muted-foreground">설정된 테이블 관계가 없습니다</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 빠른 작업 */}
|
|
<div className="pt-2 border-t">
|
|
<p className="text-xs text-muted-foreground mb-2">
|
|
더블클릭하면 화면 디자이너로 이동합니다
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|