"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { MoreHorizontal, Trash2, Copy, Plus, Search, Network, Database, Calendar, User } from "lucide-react"; import { toast } from "sonner"; import { useAuth } from "@/hooks/useAuth"; import { apiClient } from "@/lib/api/client"; // 노드 플로우 타입 정의 interface NodeFlow { flowId: number; flowName: string; flowDescription: string; createdAt: string; updatedAt: string; } interface DataFlowListProps { onLoadFlow: (flowId: number | null) => void; } export default function DataFlowList({ onLoadFlow }: DataFlowListProps) { const { user } = useAuth(); const [flows, setFlows] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); // 모달 상태 const [showCopyModal, setShowCopyModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedFlow, setSelectedFlow] = useState(null); // 노드 플로우 목록 로드 const loadFlows = useCallback(async () => { try { setLoading(true); const response = await apiClient.get("/dataflow/node-flows"); if (response.data.success) { setFlows(response.data.data); } else { throw new Error(response.data.message || "플로우 목록 조회 실패"); } } catch (error) { console.error("플로우 목록 조회 실패", error); toast.error("플로우 목록을 불러오는데 실패했습니다."); } finally { setLoading(false); } }, []); // 플로우 목록 로드 useEffect(() => { loadFlows(); }, [loadFlows]); // 플로우 삭제 const handleDelete = (flow: NodeFlow) => { setSelectedFlow(flow); setShowDeleteModal(true); }; // 플로우 복사 const handleCopy = async (flow: NodeFlow) => { try { setLoading(true); // 원본 플로우 데이터 가져오기 const response = await apiClient.get(`/dataflow/node-flows/${flow.flowId}`); if (!response.data.success) { throw new Error(response.data.message || "플로우 조회 실패"); } const originalFlow = response.data.data; // 복사본 저장 const copyResponse = await apiClient.post("/dataflow/node-flows", { flowName: `${flow.flowName} (복사본)`, flowDescription: flow.flowDescription, flowData: originalFlow.flowData, }); if (copyResponse.data.success) { toast.success(`플로우가 성공적으로 복사되었습니다`); await loadFlows(); } else { throw new Error(copyResponse.data.message || "플로우 복사 실패"); } } catch (error) { console.error("플로우 복사 실패:", error); toast.error("플로우 복사에 실패했습니다."); } finally { setLoading(false); } }; // 삭제 확인 const handleConfirmDelete = async () => { if (!selectedFlow) return; try { setLoading(true); const response = await apiClient.delete(`/dataflow/node-flows/${selectedFlow.flowId}`); if (response.data.success) { toast.success(`플로우가 삭제되었습니다: ${selectedFlow.flowName}`); await loadFlows(); } else { throw new Error(response.data.message || "플로우 삭제 실패"); } } catch (error) { console.error("플로우 삭제 실패:", error); toast.error("플로우 삭제에 실패했습니다."); } finally { setLoading(false); setShowDeleteModal(false); setSelectedFlow(null); } }; // 검색 필터링 const filteredFlows = flows.filter( (flow) => flow.flowName.toLowerCase().includes(searchTerm.toLowerCase()) || flow.flowDescription.toLowerCase().includes(searchTerm.toLowerCase()), ); return (
{/* 검색 및 액션 영역 */}
{/* 검색 영역 */}
setSearchTerm(e.target.value)} className="h-10 pl-10 text-sm" />
{/* 액션 버튼 영역 */}
{filteredFlows.length}
{loading ? ( <> {/* 데스크톱 테이블 스켈레톤 */}
플로우명 설명 생성일 최근 수정 작업 {Array.from({ length: 5 }).map((_, index) => (
))}
{/* 모바일/태블릿 카드 스켈레톤 */}
{Array.from({ length: 4 }).map((_, index) => (
))}
) : filteredFlows.length === 0 ? (

플로우가 없습니다

새 플로우를 생성하여 노드 기반 데이터 제어를 설계해보세요.

) : ( <> {/* 데스크톱 테이블 뷰 (lg 이상) */}
플로우명 설명 생성일 최근 수정 작업 {filteredFlows.map((flow) => ( onLoadFlow(flow.flowId)} >
{flow.flowName}
{flow.flowDescription || "설명 없음"}
{new Date(flow.createdAt).toLocaleDateString()}
{new Date(flow.updatedAt).toLocaleDateString()}
e.stopPropagation()}>
onLoadFlow(flow.flowId)}> 불러오기 handleCopy(flow)}> 복사 handleDelete(flow)} className="text-destructive"> 삭제
))}
{/* 모바일/태블릿 카드 뷰 (lg 미만) */}
{filteredFlows.map((flow) => (
onLoadFlow(flow.flowId)} > {/* 헤더 */}

{flow.flowName}

{flow.flowDescription || "설명 없음"}

e.stopPropagation()}> onLoadFlow(flow.flowId)}> 불러오기 handleCopy(flow)}> 복사 handleDelete(flow)} className="text-destructive"> 삭제
{/* 정보 */}
생성일 {new Date(flow.createdAt).toLocaleDateString()}
최근 수정 {new Date(flow.updatedAt).toLocaleDateString()}
))}
)} {/* 삭제 확인 모달 */} 플로우 삭제 “{selectedFlow?.flowName}” 플로우를 완전히 삭제하시겠습니까?
이 작업은 되돌릴 수 없으며, 모든 플로우 정보가 영구적으로 삭제됩니다.
); }