[agent-pipeline] pipe-20260315091327-kxyf round-1
This commit is contained in:
parent
27ce039fc8
commit
784dc73abf
|
|
@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useMemo } from "react";
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ArrowLeft, Plus, RefreshCw, Search, LayoutGrid, LayoutList, TestTube2, ChevronRight, Monitor, Database, FolderOpen } from "lucide-react";
|
||||
import { ArrowLeft, Plus, RefreshCw, Search, LayoutGrid, LayoutList, TestTube2, ChevronRight, Monitor, Database, FolderOpen, MoreHorizontal, PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
||||
import ScreenList from "@/components/screen/ScreenList";
|
||||
import ScreenDesigner from "@/components/screen/ScreenDesigner";
|
||||
import TemplateManager from "@/components/screen/TemplateManager";
|
||||
|
|
@ -16,11 +16,17 @@ import { ScreenDefinition } from "@/types/screen";
|
|||
import { screenApi } from "@/lib/api/screen";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import CreateScreenModal from "@/components/screen/CreateScreenModal";
|
||||
|
||||
// 단계별 진행을 위한 타입 정의
|
||||
type Step = "list" | "design" | "template" | "v2-test";
|
||||
type ViewMode = "tree" | "table";
|
||||
type ViewMode = "flow" | "card";
|
||||
|
||||
export default function ScreenManagementPage() {
|
||||
const searchParams = useSearchParams();
|
||||
|
|
@ -29,7 +35,7 @@ export default function ScreenManagementPage() {
|
|||
const [selectedGroup, setSelectedGroup] = useState<{ id: number; name: string; company_code?: string } | null>(null);
|
||||
const [focusedScreenIdInGroup, setFocusedScreenIdInGroup] = useState<number | null>(null);
|
||||
const [stepHistory, setStepHistory] = useState<Step[]>(["list"]);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("tree");
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("flow");
|
||||
const [screens, setScreens] = useState<ScreenDefinition[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
|
@ -162,32 +168,23 @@ export default function ScreenManagementPage() {
|
|||
return (
|
||||
<div className="flex h-screen flex-col bg-background overflow-hidden">
|
||||
{/* 페이지 헤더 */}
|
||||
<div className="relative flex-shrink-0 border-b border-border/50 bg-background/95 backdrop-blur-md px-6 py-5">
|
||||
<div className="flex-shrink-0 border-b border-border/50 bg-background/95 backdrop-blur-md px-6 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">화면 관리</h1>
|
||||
<p className="text-sm text-muted-foreground/80">화면을 그룹별로 관리하고 데이터 관계를 확인합니다</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-xl font-bold tracking-tight">화면 관리</h1>
|
||||
<Badge variant="secondary" className="text-xs">{screens.length}개 화면</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* V2 컴포넌트 테스트 버튼 */}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => goToNextStep("v2-test")}
|
||||
className="gap-2"
|
||||
>
|
||||
<TestTube2 className="h-4 w-4" />
|
||||
V2 테스트
|
||||
</Button>
|
||||
{/* 뷰 모드 전환 */}
|
||||
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as ViewMode)}>
|
||||
<TabsList className="h-9 bg-muted/50 border border-border/50">
|
||||
<TabsTrigger value="tree" className="gap-1.5 px-3 text-xs">
|
||||
<TabsTrigger value="flow" className="gap-1.5 px-3 text-xs">
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
트리
|
||||
관계도
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="table" className="gap-1.5 px-3 text-xs">
|
||||
<TabsTrigger value="card" className="gap-1.5 px-3 text-xs">
|
||||
<LayoutList className="h-4 w-4" />
|
||||
테이블
|
||||
카드
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
|
@ -198,40 +195,25 @@ export default function ScreenManagementPage() {
|
|||
<Plus className="h-4 w-4" />
|
||||
새 화면
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => goToNextStep("v2-test")}>
|
||||
<TestTube2 className="h-4 w-4 mr-2" />
|
||||
V2 테스트
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-gradient-to-r from-transparent via-primary/50 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* 통계 요약 바 */}
|
||||
<div className="flex-shrink-0 flex items-center gap-6 border-b border-border/50 bg-muted/5 px-6 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Monitor className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs text-muted-foreground">화면</span>
|
||||
<span className="text-sm font-bold">{screens.length}</span>
|
||||
</div>
|
||||
<div className="h-4 w-px bg-border" />
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-4 w-4 text-info" />
|
||||
<span className="text-xs text-muted-foreground">테이블</span>
|
||||
<span className="text-sm font-bold">{tableCount}</span>
|
||||
</div>
|
||||
{(selectedGroup || selectedScreen) && (
|
||||
<div className="ml-auto flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span>현재:</span>
|
||||
{selectedGroup && <Badge variant="outline" className="text-[10px]">{selectedGroup.name}</Badge>}
|
||||
{selectedScreen && (
|
||||
<>
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
<Badge variant="secondary" className="text-[10px]">{selectedScreen.screenName}</Badge>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 메인 콘텐츠 */}
|
||||
{viewMode === "tree" ? (
|
||||
{viewMode === "flow" ? (
|
||||
<div className="flex-1 overflow-hidden flex">
|
||||
{/* 왼쪽: 트리 구조 */}
|
||||
<div className="w-[350px] min-w-[280px] max-w-[450px] flex flex-col border-r border-border/50 bg-background/80 backdrop-blur-sm">
|
||||
|
|
@ -306,7 +288,7 @@ export default function ScreenManagementPage() {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// 테이블 뷰 (기존 ScreenList 사용)
|
||||
// 카드 뷰 (기존 ScreenList 사용)
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
<ScreenList
|
||||
onScreenSelect={handleScreenSelect}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { BaseEdge, getBezierPath, type EdgeProps } from "@xyflow/react";
|
||||
|
||||
// 커스텀 애니메이션 엣지 — bezier 곡선 + 흐르는 파티클 + 글로우 레이어
|
||||
export function AnimatedFlowEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style,
|
||||
markerEnd,
|
||||
data,
|
||||
}: EdgeProps) {
|
||||
const [edgePath] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
|
||||
const strokeColor = (style?.stroke as string) || "hsl(var(--primary))";
|
||||
const strokeW = (style?.strokeWidth as number) || 2;
|
||||
const isActive = data?.active !== false;
|
||||
const duration = data?.duration || "3s";
|
||||
const filterId = `edge-glow-${id}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 글로우용 SVG 필터 정의 (엣지별 고유 ID) */}
|
||||
<defs>
|
||||
<filter id={filterId} x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
{/* 글로우 레이어 */}
|
||||
<path
|
||||
d={edgePath}
|
||||
fill="none"
|
||||
stroke={strokeColor}
|
||||
strokeWidth={strokeW + 4}
|
||||
strokeOpacity={0.12}
|
||||
filter={`url(#${filterId})`}
|
||||
/>
|
||||
{/* 메인 엣지 */}
|
||||
<BaseEdge id={id} path={edgePath} style={style} markerEnd={markerEnd} />
|
||||
{/* 흐르는 파티클 */}
|
||||
{isActive && (
|
||||
<>
|
||||
<circle r="3" fill={strokeColor} filter={`url(#${filterId})`}>
|
||||
<animateMotion dur={duration} repeatCount="indefinite" path={edgePath} />
|
||||
</circle>
|
||||
<circle r="1.5" fill="white" opacity="0.85">
|
||||
<animateMotion dur={duration} repeatCount="indefinite" path={edgePath} />
|
||||
</circle>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue