From 01cf083da215a7504eedb3344e08a5925e49d42d Mon Sep 17 00:00:00 2001 From: caadiq Date: Mon, 2 Mar 2026 16:40:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=ED=99=9C=EB=8F=99=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=9D=BC=EC=9A=B0=ED=8A=B8/=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /admin/logs 라우트 등록, 대시보드 메뉴에 활동 로그 항목 추가 - 테이블 컬럼 비율 조정 (내용 컬럼 공간 확보) - 날짜 선택기를 커스텀 DatePicker로 교체 - 행위자 드롭다운에 애니메이션 추가 - reorder 액션 제거 Co-Authored-By: Claude Opus 4.6 --- .../pages/pc/admin/dashboard/Dashboard.jsx | 9 +- .../src/pages/pc/admin/logs/ActivityLogs.jsx | 111 ++++++++++-------- frontend/src/routes/pc/admin/index.jsx | 2 + 3 files changed, 70 insertions(+), 52 deletions(-) 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}