From e41df3b922e46e3492a98a5f69f36568a3a97424 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Mon, 9 Mar 2026 20:56:39 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260309112447-f5iu round-2 --- .../admin/automaticMng/exconList/page.tsx | 270 ++++++------- .../admin/automaticMng/flowMgmtList/page.tsx | 251 ++++++------ .../admin/screenMng/dashboardList/page.tsx | 369 ++++++------------ 3 files changed, 374 insertions(+), 516 deletions(-) diff --git a/frontend/app/(main)/admin/automaticMng/exconList/page.tsx b/frontend/app/(main)/admin/automaticMng/exconList/page.tsx index 0ab2fbeb..89ecba46 100644 --- a/frontend/app/(main)/admin/automaticMng/exconList/page.tsx +++ b/frontend/app/(main)/admin/automaticMng/exconList/page.tsx @@ -4,10 +4,8 @@ import React, { useState, useEffect } from "react"; import { Plus, Search, Pencil, Trash2, Database, Terminal, Globe } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, @@ -24,11 +22,12 @@ import { ExternalDbConnectionAPI, ExternalDbConnection, ExternalDbConnectionFilter, - ConnectionTestRequest, } from "@/lib/api/externalDbConnection"; import { ExternalDbConnectionModal } from "@/components/admin/ExternalDbConnectionModal"; import { SqlQueryModal } from "@/components/admin/SqlQueryModal"; import { RestApiConnectionList } from "@/components/admin/RestApiConnectionList"; +import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; +import { ScrollToTop } from "@/components/common/ScrollToTop"; type ConnectionTabType = "database" | "rest-api"; @@ -102,7 +101,6 @@ export default function ExternalConnectionsPage() { setSupportedDbTypes([{ value: "ALL", label: "전체" }, ...types]); } catch (error) { console.error("지원 DB 타입 로딩 오류:", error); - // 실패 시 기본값 사용 setSupportedDbTypes([ { value: "ALL", label: "전체" }, { value: "mysql", label: "MySQL" }, @@ -114,45 +112,36 @@ export default function ExternalConnectionsPage() { } }; - // 초기 데이터 로딩 useEffect(() => { loadConnections(); loadSupportedDbTypes(); }, []); - // 필터 변경 시 데이터 재로딩 useEffect(() => { loadConnections(); }, [searchTerm, dbTypeFilter, activeStatusFilter]); - // 새 연결 추가 const handleAddConnection = () => { setEditingConnection(undefined); setIsModalOpen(true); }; - // 연결 편집 const handleEditConnection = (connection: ExternalDbConnection) => { setEditingConnection(connection); setIsModalOpen(true); }; - // 연결 삭제 확인 다이얼로그 열기 const handleDeleteConnection = (connection: ExternalDbConnection) => { setConnectionToDelete(connection); setDeleteDialogOpen(true); }; - // 연결 삭제 실행 const confirmDeleteConnection = async () => { if (!connectionToDelete?.id) return; try { await ExternalDbConnectionAPI.deleteConnection(connectionToDelete.id); - toast({ - title: "성공", - description: "연결이 삭제되었습니다.", - }); + toast({ title: "성공", description: "연결이 삭제되었습니다." }); loadConnections(); } catch (error) { console.error("연결 삭제 오류:", error); @@ -167,13 +156,11 @@ export default function ExternalConnectionsPage() { } }; - // 연결 삭제 취소 const cancelDeleteConnection = () => { setDeleteDialogOpen(false); setConnectionToDelete(null); }; - // 연결 테스트 const handleTestConnection = async (connection: ExternalDbConnection) => { if (!connection.id) return; @@ -181,14 +168,10 @@ export default function ExternalConnectionsPage() { try { const result = await ExternalDbConnectionAPI.testConnection(connection.id); - setTestResults((prev) => new Map(prev).set(connection.id!, result.success)); if (result.success) { - toast({ - title: "연결 성공", - description: `${connection.connection_name} 연결이 성공했습니다.`, - }); + toast({ title: "연결 성공", description: `${connection.connection_name} 연결이 성공했습니다.` }); } else { toast({ title: "연결 실패", @@ -199,11 +182,7 @@ export default function ExternalConnectionsPage() { } catch (error) { console.error("연결 테스트 오류:", error); setTestResults((prev) => new Map(prev).set(connection.id!, false)); - toast({ - title: "연결 테스트 오류", - description: "연결 테스트 중 오류가 발생했습니다.", - variant: "destructive", - }); + toast({ title: "연결 테스트 오류", description: "연결 테스트 중 오류가 발생했습니다.", variant: "destructive" }); } finally { setTestingConnections((prev) => { const newSet = new Set(prev); @@ -213,19 +192,77 @@ export default function ExternalConnectionsPage() { } }; - // 모달 저장 처리 const handleModalSave = () => { setIsModalOpen(false); setEditingConnection(undefined); loadConnections(); }; - // 모달 취소 처리 const handleModalCancel = () => { setIsModalOpen(false); setEditingConnection(undefined); }; + // 테이블 컬럼 정의 + const columns: RDVColumn[] = [ + { key: "connection_name", label: "연결명", + render: (v) => {v} }, + { key: "company_code", label: "회사", width: "100px", + render: (_v, row) => (row as any).company_name || row.company_code }, + { key: "db_type", label: "DB 타입", width: "120px", + render: (v) => {DB_TYPE_LABELS[v] || v} }, + { key: "host", label: "호스트:포트", width: "180px", hideOnMobile: true, + render: (_v, row) => {row.host}:{row.port} }, + { key: "database_name", label: "데이터베이스", width: "140px", hideOnMobile: true, + render: (v) => {v} }, + { key: "username", label: "사용자", width: "100px", hideOnMobile: true, + render: (v) => {v} }, + { key: "is_active", label: "상태", width: "80px", + render: (v) => ( + + {v === "Y" ? "활성" : "비활성"} + + ) }, + { key: "created_date", label: "생성일", width: "100px", hideOnMobile: true, + render: (v) => ( + + {v ? new Date(v).toLocaleDateString() : "N/A"} + + ) }, + { key: "id", label: "연결 테스트", width: "150px", hideOnMobile: true, + render: (_v, row) => ( +
+ + {testResults.has(row.id!) && ( + + {testResults.get(row.id!) ? "성공" : "실패"} + + )} +
+ ) }, + ]; + + // 모바일 카드 필드 정의 + const cardFields: RDVCardField[] = [ + { label: "DB 타입", + render: (c) => {DB_TYPE_LABELS[c.db_type] || c.db_type} }, + { label: "호스트", + render: (c) => {c.host}:{c.port} }, + { label: "데이터베이스", + render: (c) => {c.database_name} }, + { label: "상태", + render: (c) => ( + + {c.is_active === "Y" ? "활성" : "비활성"} + + ) }, + ]; + return (
@@ -237,7 +274,7 @@ export default function ExternalConnectionsPage() { {/* 탭 */} setActiveTab(value as ConnectionTabType)}> - + 데이터베이스 연결 @@ -252,8 +289,7 @@ export default function ExternalConnectionsPage() { {/* 검색 및 필터 */}
-
- {/* 검색 */} +
- - {/* DB 타입 필터 */} - - {/* 활성 상태 필터 */}
- - {/* 추가 버튼 */}
- {/* 연결 목록 */} - {loading ? ( -
-
로딩 중...
-
- ) : connections.length === 0 ? ( -
-
-

등록된 연결이 없습니다

-
-
- ) : ( -
- - - - 연결명 - 회사 - DB 타입 - 호스트:포트 - 데이터베이스 - 사용자 - 상태 - 생성일 - 연결 테스트 - 작업 - - - - {connections.map((connection) => ( - - -
{connection.connection_name}
-
- - {(connection as any).company_name || connection.company_code} - - - - {DB_TYPE_LABELS[connection.db_type] || connection.db_type} - - - - {connection.host}:{connection.port} - - {connection.database_name} - {connection.username} - - - {connection.is_active === "Y" ? "활성" : "비활성"} - - - - {connection.created_date ? new Date(connection.created_date).toLocaleDateString() : "N/A"} - - -
- - {testResults.has(connection.id!) && ( - - {testResults.get(connection.id!) ? "성공" : "실패"} - - )} -
-
- -
- - - -
-
-
- ))} -
-
-
- )} + {/* 연결 목록 - ResponsiveDataView */} + String(c.id || c.connection_name)} + isLoading={loading} + emptyMessage="등록된 연결이 없습니다" + skeletonCount={5} + cardTitle={(c) => c.connection_name} + cardSubtitle={(c) => {c.host}:{c.port}/{c.database_name}} + cardHeaderRight={(c) => ( + + {c.is_active === "Y" ? "활성" : "비활성"} + + )} + cardFields={cardFields} + renderActions={(c) => ( + <> + + + + + + )} + actionsLabel="작업" + actionsWidth="180px" + /> {/* 연결 설정 모달 */} {isModalOpen && ( @@ -430,7 +399,7 @@ export default function ExternalConnectionsPage() { 연결 삭제 확인 - "{connectionToDelete?.connection_name}" 연결을 삭제하시겠습니까? + “{connectionToDelete?.connection_name}” 연결을 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다.
@@ -472,6 +441,7 @@ export default function ExternalConnectionsPage() {
+
); } diff --git a/frontend/app/(main)/admin/automaticMng/flowMgmtList/page.tsx b/frontend/app/(main)/admin/automaticMng/flowMgmtList/page.tsx index 6435274e..b690bd30 100644 --- a/frontend/app/(main)/admin/automaticMng/flowMgmtList/page.tsx +++ b/frontend/app/(main)/admin/automaticMng/flowMgmtList/page.tsx @@ -9,7 +9,7 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -import { Plus, Edit2, Trash2, Workflow, Table, Calendar, User, Check, ChevronsUpDown } from "lucide-react"; +import { Plus, Edit2, Trash2, Workflow, Search, Check, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -35,6 +35,7 @@ import { tableManagementApi } from "@/lib/api/tableManagement"; import { ScrollToTop } from "@/components/common/ScrollToTop"; import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection"; import { ExternalRestApiConnectionAPI, ExternalRestApiConnection } from "@/lib/api/externalRestApiConnection"; +import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; export default function FlowManagementPage() { const router = useRouter(); @@ -513,6 +514,81 @@ export default function FlowManagementPage() { router.push(`/admin/flow-management/${flowId}`); }; + // 검색 필터 상태 + const [searchText, setSearchText] = useState(""); + + // 검색 필터링된 플로우 목록 + const filteredFlows = searchText + ? flows.filter( + (f) => + f.name.toLowerCase().includes(searchText.toLowerCase()) || + f.tableName?.toLowerCase().includes(searchText.toLowerCase()) || + f.description?.toLowerCase().includes(searchText.toLowerCase()), + ) + : flows; + + // ResponsiveDataView 컬럼 정의 + const columns: RDVColumn[] = [ + { + key: "name", + label: "플로우명", + render: (_v, row) => ( +
+ + {row.isActive && ( + 활성 + )} +
+ ), + }, + { + key: "description", + label: "설명", + render: (_v, row) => ( + + {row.description || "-"} + + ), + }, + { + key: "tableName", + label: "연결 테이블", + render: (_v, row) => ( + {row.tableName} + ), + }, + { + key: "createdBy", + label: "생성자", + width: "120px", + }, + { + key: "updatedAt", + label: "수정일", + width: "120px", + render: (_v, row) => new Date(row.updatedAt).toLocaleDateString("ko-KR"), + }, + ]; + + // 모바일 카드 필드 정의 + const cardFields: RDVCardField[] = [ + { label: "설명", render: (f) => f.description || "-" }, + { + label: "테이블", + render: (f) => {f.tableName}, + }, + { label: "생성자", render: (f) => f.createdBy }, + { + label: "수정일", + render: (f) => new Date(f.updatedAt).toLocaleDateString("ko-KR"), + }, + ]; + return (
@@ -522,123 +598,74 @@ export default function FlowManagementPage() {

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

- {/* 액션 버튼 영역 */} -
+ {/* 검색 툴바 (반응형) */} +
+
+
+ + setSearchText(e.target.value)} + className="h-10 pl-10 text-sm" + /> +
+
+ 총 {filteredFlows.length} 건 +
+
- {/* 플로우 카드 목록 */} - {loading ? ( -
- {Array.from({ length: 6 }).map((_, index) => ( -
-
-
-
-
-
-
- {Array.from({ length: 3 }).map((_, i) => ( -
-
-
-
- ))} -
-
-
-
-
-
- ))} -
- ) : flows.length === 0 ? ( -
-
-
- -
-

생성된 플로우가 없습니다

-

- 새 플로우를 생성하여 업무 프로세스를 관리해보세요. -

- -
-
- ) : ( -
- {flows.map((flow) => ( -
handleEdit(flow.id)} + {/* 플로우 목록 (ResponsiveDataView) */} + + data={filteredFlows} + columns={columns} + keyExtractor={(f) => String(f.id)} + isLoading={loading} + emptyMessage="생성된 플로우가 없습니다." + skeletonCount={6} + cardTitle={(f) => f.name} + cardSubtitle={(f) => f.description || "설명 없음"} + cardHeaderRight={(f) => + f.isActive ? ( + 활성 + ) : null + } + cardFields={cardFields} + onRowClick={(f) => handleEdit(f.id)} + renderActions={(f) => ( + <> +
- ) : dashboards.length === 0 ? ( -
-
-

대시보드가 없습니다

-
-
) : ( - <> - {/* 데스크톱 테이블 뷰 (lg 이상) */} -
- - - - 제목 - 설명 - 생성자 - 생성일 - 수정일 - 작업 - - - - {dashboards.map((dashboard) => ( - - - - - - {dashboard.description || "-"} - - - {dashboard.createdByName || dashboard.createdBy || "-"} - - - {formatDate(dashboard.createdAt)} - - - {formatDate(dashboard.updatedAt)} - - - - - - - - router.push(`/admin/screenMng/dashboardList/${dashboard.id}`)} - className="gap-2 text-sm" - > - - 편집 - - handleCopy(dashboard)} className="gap-2 text-sm"> - - 복사 - - handleDeleteClick(dashboard.id, dashboard.title)} - className="text-destructive focus:text-destructive gap-2 text-sm" - > - - 삭제 - - - - - - ))} - -
-
- - {/* 모바일/태블릿 카드 뷰 (lg 미만) */} -
- {dashboards.map((dashboard) => ( -
- {/* 헤더 */} -
-
- -

{dashboard.id}

-
-
- - {/* 정보 */} -
-
- 설명 - {dashboard.description || "-"} -
-
- 생성자 - {dashboard.createdByName || dashboard.createdBy || "-"} -
-
- 생성일 - {formatDate(dashboard.createdAt)} -
-
- 수정일 - {formatDate(dashboard.updatedAt)} -
-
- - {/* 액션 */} -
- - - -
-
- ))} -
- + + data={dashboards} + columns={columns} + keyExtractor={(d) => d.id} + isLoading={loading} + emptyMessage="대시보드가 없습니다." + skeletonCount={10} + cardTitle={(d) => d.title} + cardSubtitle={(d) => d.id} + cardFields={cardFields} + onRowClick={(d) => router.push(`/admin/screenMng/dashboardList/${d.id}`)} + renderActions={(d) => ( + + + + + + router.push(`/admin/screenMng/dashboardList/${d.id}`)} + className="gap-2 text-sm" + > + + 편집 + + handleCopy(d)} className="gap-2 text-sm"> + + 복사 + + handleDeleteClick(d.id, d.title)} + className="text-destructive focus:text-destructive gap-2 text-sm" + > + + 삭제 + + + + )} + actionsLabel="작업" + actionsWidth="80px" + /> )} {/* 페이지네이션 */} @@ -453,6 +311,9 @@ export default function DashboardListPage() { onConfirm={handleDeleteConfirm} />
+ + {/* Scroll to Top 버튼 */} +
); }