"use client"; /** * 플로우 관리 메인 페이지 * - 플로우 정의 목록 * - 플로우 생성/수정/삭제 * - 플로우 편집기로 이동 */ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Plus, Edit2, Trash2, Play, Workflow, Table, Calendar, User, Check, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { getFlowDefinitions, createFlowDefinition, deleteFlowDefinition } from "@/lib/api/flow"; import { FlowDefinition } from "@/types/flow"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { tableManagementApi } from "@/lib/api/tableManagement"; export default function FlowManagementPage() { const router = useRouter(); const { toast } = useToast(); // 상태 const [flows, setFlows] = useState([]); const [loading, setLoading] = useState(true); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedFlow, setSelectedFlow] = useState(null); // 테이블 목록 관련 상태 const [tableList, setTableList] = useState([]); // 내부 DB 테이블 const [loadingTables, setLoadingTables] = useState(false); const [openTableCombobox, setOpenTableCombobox] = useState(false); const [selectedDbSource, setSelectedDbSource] = useState<"internal" | number>("internal"); // "internal" 또는 외부 DB connection ID const [externalConnections, setExternalConnections] = useState([]); const [externalTableList, setExternalTableList] = useState([]); const [loadingExternalTables, setLoadingExternalTables] = useState(false); // 생성 폼 상태 const [formData, setFormData] = useState({ name: "", description: "", tableName: "", }); // 플로우 목록 조회 const loadFlows = async () => { setLoading(true); try { const response = await getFlowDefinitions({ isActive: true }); if (response.success && response.data) { setFlows(response.data); } else { toast({ title: "조회 실패", description: response.error || "플로우 목록을 불러올 수 없습니다.", variant: "destructive", }); } } catch (error: any) { toast({ title: "오류 발생", description: error.message, variant: "destructive", }); } finally { setLoading(false); } }; useEffect(() => { loadFlows(); }, []); // 테이블 목록 로드 (내부 DB) useEffect(() => { const loadTables = async () => { try { setLoadingTables(true); const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setTableList(response.data); } } catch (error) { console.error("Failed to load tables:", error); } finally { setLoadingTables(false); } }; loadTables(); }, []); // 외부 DB 연결 목록 로드 useEffect(() => { const loadConnections = async () => { try { const token = localStorage.getItem("authToken"); if (!token) { console.warn("No auth token found"); return; } const response = await fetch("/api/external-db-connections/control/active", { headers: { Authorization: `Bearer ${token}`, }, }); if (response && response.ok) { const data = await response.json(); if (data.success && data.data) { // 메인 데이터베이스(현재 시스템) 제외 - connection_name에 "메인" 또는 "현재 시스템"이 포함된 것 필터링 const filtered = data.data.filter( (conn: any) => !conn.connection_name.includes("메인") && !conn.connection_name.includes("현재 시스템"), ); setExternalConnections(filtered); } } } catch (error) { console.error("Failed to load external connections:", error); setExternalConnections([]); } }; loadConnections(); }, []); // 외부 DB 테이블 목록 로드 useEffect(() => { if (selectedDbSource === "internal" || !selectedDbSource) { setExternalTableList([]); return; } const loadExternalTables = async () => { try { setLoadingExternalTables(true); const token = localStorage.getItem("authToken"); const response = await fetch(`/api/multi-connection/connections/${selectedDbSource}/tables`, { headers: { Authorization: `Bearer ${token}`, }, }); if (response && response.ok) { const data = await response.json(); if (data.success && data.data) { const tables = Array.isArray(data.data) ? data.data : []; const tableNames = tables .map((t: any) => (typeof t === "string" ? t : t.tableName || t.table_name || t.tablename || t.name)) .filter(Boolean); setExternalTableList(tableNames); } else { setExternalTableList([]); } } else { setExternalTableList([]); } } catch (error) { console.error("외부 DB 테이블 목록 조회 오류:", error); setExternalTableList([]); } finally { setLoadingExternalTables(false); } }; loadExternalTables(); }, [selectedDbSource]); // 플로우 생성 const handleCreate = async () => { console.log("🚀 handleCreate called with formData:", formData); if (!formData.name || !formData.tableName) { console.log("❌ Validation failed:", { name: formData.name, tableName: formData.tableName }); toast({ title: "입력 오류", description: "플로우 이름과 테이블 이름은 필수입니다.", variant: "destructive", }); return; } try { // DB 소스 정보 추가 const requestData = { ...formData, dbSourceType: selectedDbSource === "internal" ? "internal" : "external", dbConnectionId: selectedDbSource === "internal" ? undefined : Number(selectedDbSource), }; console.log("✅ Calling createFlowDefinition with:", requestData); const response = await createFlowDefinition(requestData); if (response.success && response.data) { toast({ title: "생성 완료", description: "플로우가 성공적으로 생성되었습니다.", }); setIsCreateDialogOpen(false); setFormData({ name: "", description: "", tableName: "" }); setSelectedDbSource("internal"); loadFlows(); } else { toast({ title: "생성 실패", description: response.error || response.message, variant: "destructive", }); } } catch (error: any) { toast({ title: "오류 발생", description: error.message, variant: "destructive", }); } }; // 플로우 삭제 const handleDelete = async () => { if (!selectedFlow) return; try { const response = await deleteFlowDefinition(selectedFlow.id); if (response.success) { toast({ title: "삭제 완료", description: "플로우가 삭제되었습니다.", }); setIsDeleteDialogOpen(false); setSelectedFlow(null); loadFlows(); } else { toast({ title: "삭제 실패", description: response.error, variant: "destructive", }); } } catch (error: any) { toast({ title: "오류 발생", description: error.message, variant: "destructive", }); } }; // 플로우 편집기로 이동 const handleEdit = (flowId: number) => { router.push(`/admin/flow-management/${flowId}`); }; return (
{/* 헤더 */}

플로우 관리

업무 프로세스 플로우를 생성하고 관리합니다

{/* 플로우 카드 목록 */} {loading ? (

로딩 중...

) : flows.length === 0 ? (

생성된 플로우가 없습니다

) : (
{flows.map((flow) => ( handleEdit(flow.id)} >
{flow.name} {flow.isActive && ( 활성 )} {flow.description || "설명 없음"}
{flow.tableName}
생성자: {flow.createdBy}
{new Date(flow.updatedAt).toLocaleDateString("ko-KR")}
))} )} {/* 생성 다이얼로그 */} 새 플로우 생성 새로운 업무 프로세스 플로우를 생성합니다
setFormData({ ...formData, name: e.target.value })} placeholder="예: 제품 수명주기 관리" className="h-8 text-xs sm:h-10 sm:text-sm" />
{/* DB 소스 선택 */}

플로우에서 사용할 데이터베이스를 선택합니다

{/* 테이블 선택 */}
테이블을 찾을 수 없습니다. {selectedDbSource === "internal" ? // 내부 DB 테이블 목록 tableList.map((table) => ( { console.log("📝 Internal table selected:", { tableName: table.tableName, currentValue, }); setFormData({ ...formData, tableName: currentValue }); setOpenTableCombobox(false); }} className="text-xs sm:text-sm" >
{table.displayName || table.tableName} {table.description && ( {table.description} )}
)) : // 외부 DB 테이블 목록 externalTableList.map((tableName, index) => ( { setFormData({ ...formData, tableName: currentValue }); setOpenTableCombobox(false); }} className="text-xs sm:text-sm" >
{tableName}
))}

플로우의 모든 단계에서 사용할 기본 테이블입니다 (단계마다 상태 컬럼만 지정합니다)