"use client"; import React, { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; 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 { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Plus, Search, MoreHorizontal, Edit, Trash2, Play, RefreshCw, BarChart3 } from "lucide-react"; import { toast } from "sonner"; import { BatchAPI, BatchJob } from "@/lib/api/batch"; import BatchJobModal from "@/components/admin/BatchJobModal"; export default function BatchManagementPage() { const [jobs, setJobs] = useState([]); const [filteredJobs, setFilteredJobs] = useState([]); const [isLoading, setIsLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [typeFilter, setTypeFilter] = useState("all"); const [jobTypes, setJobTypes] = useState>([]); // 모달 상태 const [isModalOpen, setIsModalOpen] = useState(false); const [selectedJob, setSelectedJob] = useState(null); useEffect(() => { loadJobs(); loadJobTypes(); }, []); useEffect(() => { filterJobs(); }, [jobs, searchTerm, statusFilter, typeFilter]); const loadJobs = async () => { setIsLoading(true); try { const data = await BatchAPI.getBatchJobs(); setJobs(data); } catch (error) { console.error("배치 작업 목록 조회 오류:", error); toast.error("배치 작업 목록을 불러오는데 실패했습니다."); } finally { setIsLoading(false); } }; const loadJobTypes = async () => { try { const types = await BatchAPI.getSupportedJobTypes(); setJobTypes(types); } catch (error) { console.error("작업 타입 조회 오류:", error); } }; const filterJobs = () => { let filtered = jobs; // 검색어 필터 if (searchTerm) { filtered = filtered.filter(job => job.job_name.toLowerCase().includes(searchTerm.toLowerCase()) || job.description?.toLowerCase().includes(searchTerm.toLowerCase()) ); } // 상태 필터 if (statusFilter !== "all") { filtered = filtered.filter(job => job.is_active === statusFilter); } // 타입 필터 if (typeFilter !== "all") { filtered = filtered.filter(job => job.job_type === typeFilter); } setFilteredJobs(filtered); }; const handleCreate = () => { setSelectedJob(null); setIsModalOpen(true); }; const handleEdit = (job: BatchJob) => { setSelectedJob(job); setIsModalOpen(true); }; const handleDelete = async (job: BatchJob) => { if (!confirm(`"${job.job_name}" 배치 작업을 삭제하시겠습니까?`)) { return; } try { await BatchAPI.deleteBatchJob(job.id!); toast.success("배치 작업이 삭제되었습니다."); loadJobs(); } catch (error) { console.error("배치 작업 삭제 오류:", error); toast.error("배치 작업 삭제에 실패했습니다."); } }; const handleExecute = async (job: BatchJob) => { try { await BatchAPI.executeBatchJob(job.id!); toast.success(`"${job.job_name}" 배치 작업을 실행했습니다.`); } catch (error) { console.error("배치 작업 실행 오류:", error); toast.error("배치 작업 실행에 실패했습니다."); } }; const handleModalSave = () => { loadJobs(); }; const getStatusBadge = (isActive: string) => { return isActive === "Y" ? ( 활성 ) : ( 비활성 ); }; const getTypeBadge = (type: string) => { const option = jobTypes.find(opt => opt.value === type); const colors = { collection: "bg-blue-100 text-blue-800", sync: "bg-purple-100 text-purple-800", cleanup: "bg-orange-100 text-orange-800", custom: "bg-gray-100 text-gray-800", }; const icons = { collection: "📥", sync: "🔄", cleanup: "🧹", custom: "⚙️", }; return ( {icons[type as keyof typeof icons] || "📋"} {option?.label || type} ); }; const getSuccessRate = (job: BatchJob) => { if (job.execution_count === 0) return 100; return Math.round((job.success_count / job.execution_count) * 100); }; return (
{/* 헤더 */}

배치 관리

스케줄된 배치 작업을 관리하고 실행 상태를 모니터링합니다.

{/* 통계 카드 */}
총 작업
📋
{jobs.length}

활성: {jobs.filter(j => j.is_active === 'Y').length}개

총 실행
▶️
{jobs.reduce((sum, job) => sum + job.execution_count, 0)}

누적 실행 횟수

성공
{jobs.reduce((sum, job) => sum + job.success_count, 0)}

총 성공 횟수

실패
{jobs.reduce((sum, job) => sum + job.failure_count, 0)}

총 실패 횟수

{/* 필터 및 검색 */} 필터 및 검색
setSearchTerm(e.target.value)} className="pl-10" />
{/* 배치 작업 목록 */} 배치 작업 목록 ({filteredJobs.length}개) {isLoading ? (

배치 작업을 불러오는 중...

) : filteredJobs.length === 0 ? (
{jobs.length === 0 ? "배치 작업이 없습니다." : "검색 결과가 없습니다."}
) : ( 작업명 타입 스케줄 상태 실행 통계 성공률 마지막 실행 작업 {filteredJobs.map((job) => (
{job.job_name}
{job.description && (
{job.description}
)}
{getTypeBadge(job.job_type)} {job.schedule_cron || "-"} {getStatusBadge(job.is_active)}
총 {job.execution_count}회
성공 {job.success_count} / 실패 {job.failure_count}
= 90 ? 'text-green-600' : getSuccessRate(job) >= 70 ? 'text-yellow-600' : 'text-red-600' }`}> {getSuccessRate(job)}%
{job.last_executed_at ? new Date(job.last_executed_at).toLocaleString() : "-"} handleEdit(job)}> 수정 handleExecute(job)} disabled={job.is_active !== "Y"} > 실행 handleDelete(job)}> 삭제
))}
)}
{/* 배치 작업 모달 */} setIsModalOpen(false)} onSave={handleModalSave} job={selectedJob} />
); }