diff --git a/frontend/src/pages/pc/admin/dashboard/Dashboard.jsx b/frontend/src/pages/pc/admin/dashboard/Dashboard.jsx index 4a30d65..6485cbd 100644 --- a/frontend/src/pages/pc/admin/dashboard/Dashboard.jsx +++ b/frontend/src/pages/pc/admin/dashboard/Dashboard.jsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { motion } from 'framer-motion'; -import { Disc3, Calendar, Users, Home, ChevronRight } from 'lucide-react'; +import { Disc3, Calendar, Users, Home, ChevronRight, ScrollText } from 'lucide-react'; import { AdminLayout } from '@/components/pc/admin'; import { useAdminAuth } from '@/hooks/pc/admin'; import { adminStatsApi } from '@/api/admin'; @@ -88,6 +88,13 @@ function AdminDashboard() { path: '/admin/schedule', color: 'bg-blue-500', }, + { + icon: ScrollText, + label: '활동 로그', + description: '관리자 및 봇 활동 기록 조회', + path: '/admin/logs', + color: 'bg-gray-500', + }, ]; return ( diff --git a/frontend/src/pages/pc/admin/logs/ActivityLogs.jsx b/frontend/src/pages/pc/admin/logs/ActivityLogs.jsx index 1df507d..06e79f0 100644 --- a/frontend/src/pages/pc/admin/logs/ActivityLogs.jsx +++ b/frontend/src/pages/pc/admin/logs/ActivityLogs.jsx @@ -3,12 +3,12 @@ */ import { useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; import { Home, ChevronRight, Search, ChevronLeft, ChevronDown, User, Bot, ScrollText, X, } from 'lucide-react'; -import { AdminLayout } from '@/components/pc/admin'; +import { AdminLayout, DatePicker } from '@/components/pc/admin'; import { useAdminAuth } from '@/hooks/pc/admin'; // 더미 데이터 @@ -22,7 +22,7 @@ const DUMMY_LOGS = [ { id: 7, actor: 'admin', action: 'delete', category: 'schedule', target_type: 'youtube_schedule', target_id: 456, summary: 'YouTube 일정 삭제: 이전 영상', details: null, created_at: '2026-03-02T14:00:00' }, { id: 8, actor: 'admin', action: 'create', category: 'concert', target_type: 'concert', target_id: 5, summary: '콘서트 일정 생성: fromis_9 팬미팅', details: null, created_at: '2026-03-02T13:55:00' }, { id: 9, actor: 'admin', action: 'update', category: 'category', target_type: 'category', target_id: 3, summary: '카테고리 수정: 음악방송', details: null, created_at: '2026-03-02T13:50:00' }, - { id: 10, actor: 'admin', action: 'reorder', category: 'category', target_type: 'category', target_id: null, summary: '카테고리 순서 변경', details: null, created_at: '2026-03-02T13:45:00' }, + { id: 10, actor: 'admin', action: 'update', category: 'category', target_type: 'category', target_id: null, summary: '카테고리 순서 변경', details: null, created_at: '2026-03-02T13:45:00' }, { id: 11, actor: 'youtube-1', action: 'error', category: 'sync', target_type: 'youtube_bot', target_id: 1, summary: '동기화 에러: API 할당량 초과', details: { error: 'quotaExceeded' }, created_at: '2026-03-02T13:40:00' }, { id: 12, actor: 'admin', action: 'start', category: 'bot', target_type: 'youtube_bot', target_id: 3, summary: 'YouTube 봇 시작: 스프', details: null, created_at: '2026-03-02T13:35:00' }, { id: 13, actor: 'admin', action: 'stop', category: 'bot', target_type: 'youtube_bot', target_id: 2, summary: 'YouTube 봇 정지: 채널 비활성화', details: null, created_at: '2026-03-02T13:30:00' }, @@ -57,7 +57,6 @@ const ACTION_STYLES = { create: 'bg-emerald-100 text-emerald-700', upload: 'bg-emerald-100 text-emerald-700', update: 'bg-blue-100 text-blue-700', - reorder: 'bg-blue-100 text-blue-700', delete: 'bg-red-100 text-red-700', sync_complete: 'bg-purple-100 text-purple-700', error: 'bg-red-100 text-red-700', @@ -70,7 +69,6 @@ const ACTION_LABELS = { create: '생성', upload: '업로드', update: '수정', - reorder: '순서변경', delete: '삭제', sync_complete: '동기화', error: '에러', @@ -137,11 +135,12 @@ function ActivityLogs() { // 날짜/시간 포맷 const formatDateTime = (dateStr) => { const date = new Date(dateStr); + const y = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${month}.${day} ${hours}:${minutes}`; + return `${y}.${month}.${day} ${hours}:${minutes}`; }; // 행위자 아이콘 @@ -219,45 +218,55 @@ function ActivityLogs() { - {actorDropdownOpen && ( - <> -
setActorDropdownOpen(false)} /> -
- {[ - { value: 'all', label: '전체 행위자' }, - { value: 'admin', label: '관리자' }, - { value: 'bot', label: '봇' }, - ].map((opt) => ( - - ))} -
- - )} + + {actorDropdownOpen && ( + <> +
setActorDropdownOpen(false)} /> + + {[ + { value: 'all', label: '전체 행위자' }, + { value: 'admin', label: '관리자' }, + { value: 'bot', label: '봇' }, + ].map((opt) => ( + + ))} + + + )} +
{/* 날짜 필터 */}
- { setDateFrom(e.target.value); setCurrentPage(1); }} - className="px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-gray-600" - /> +
+ { setDateFrom(v); setCurrentPage(1); }} + placeholder="시작일" + /> +
~ - { setDateTo(e.target.value); setCurrentPage(1); }} - className="px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-gray-600" - /> +
+ { setDateTo(v); setCurrentPage(1); }} + placeholder="종료일" + /> +
{/* 필터 초기화 */} @@ -305,14 +314,14 @@ function ActivityLogs() { transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1], delay: 0.15 }} className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden" > - +
- - - - - + + + + + @@ -324,23 +333,23 @@ function ActivityLogs() { transition={{ delay: index * 0.03 }} className="hover:bg-gray-50 transition-colors" > - - - - - diff --git a/frontend/src/routes/pc/admin/index.jsx b/frontend/src/routes/pc/admin/index.jsx index afee464..12e2cd9 100644 --- a/frontend/src/routes/pc/admin/index.jsx +++ b/frontend/src/routes/pc/admin/index.jsx @@ -35,6 +35,7 @@ import AdminYouTubeEditForm from '@/pages/pc/admin/schedules/edit/YouTubeEditFor import AdminScheduleCategory from '@/pages/pc/admin/schedules/ScheduleCategory'; import AdminScheduleDict from '@/pages/pc/admin/schedules/ScheduleDict'; import AdminScheduleBots from '@/pages/pc/admin/schedules/ScheduleBots'; +import AdminActivityLogs from '@/pages/pc/admin/logs/ActivityLogs'; import AdminNotFound from '@/pages/pc/admin/common/NotFound'; /** @@ -59,6 +60,7 @@ export default function AdminRoutes() { } /> } /> } /> + } /> } /> );
시간행위자액션카테고리내용시간행위자액션카테고리내용
+ {formatDateTime(log.created_at)} + {renderActorBadge(log.actor)} + {ACTION_LABELS[log.action] || log.action} + {CATEGORIES.find((c) => c.value === log.category)?.label || log.category} + {log.summary}