봇 관리
일정 자동화 봇을 관리합니다
YouTube API 할당량 경고
{quotaWarning.message}
봇 목록
등록된 봇이 없습니다
위의 버튼을 클릭하여 봇을 추가하세요
{bot.name}
{bot.last_check_at ? `${formatTime(bot.last_check_at)}에 업데이트됨` : '아직 업데이트 없음'}
import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
Home, ChevronRight, Bot, Play, Square,
Youtube, Calendar, Clock, CheckCircle, XCircle, RefreshCw, Download
} from 'lucide-react';
import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip';
import AdminHeader from '../../../components/admin/AdminHeader';
import useToast from '../../../hooks/useToast';
import * as botsApi from '../../../api/admin/bots';
// X 아이콘 컴포넌트
const XIcon = ({ size = 20, fill = "currentColor" }) => (
);
function AdminScheduleBots() {
const navigate = useNavigate();
const [user, setUser] = useState(null);
const { toast, setToast } = useToast();
const [bots, setBots] = useState([]);
const [loading, setLoading] = useState(true);
const [isInitialLoad, setIsInitialLoad] = useState(true); // 첫 로드 여부 (애니메이션용)
const [syncing, setSyncing] = useState(null); // 동기화 중인 봇 ID
const [quotaWarning, setQuotaWarning] = useState(null); // 할당량 경고 상태
useEffect(() => {
const token = localStorage.getItem('adminToken');
const userData = localStorage.getItem('adminUser');
if (!token || !userData) {
navigate('/admin');
return;
}
setUser(JSON.parse(userData));
fetchBots();
fetchQuotaWarning();
}, [navigate]);
// 봇 목록 조회
const fetchBots = async () => {
setLoading(true);
try {
const data = await botsApi.getBots();
setBots(data);
} catch (error) {
console.error('봇 목록 조회 오류:', error);
setToast({ type: 'error', message: '봇 목록을 불러올 수 없습니다.' });
} finally {
setLoading(false);
}
};
// 할당량 경고 상태 조회
const fetchQuotaWarning = async () => {
try {
const data = await botsApi.getQuotaWarning();
if (data.active) {
setQuotaWarning(data);
}
} catch (error) {
console.error('할당량 경고 조회 오류:', error);
}
};
// 할당량 경고 해제
const handleDismissQuotaWarning = async () => {
try {
await botsApi.dismissQuotaWarning();
setQuotaWarning(null);
} catch (error) {
console.error('할당량 경고 해제 오류:', error);
}
};
// 봇 시작/정지 토글
const toggleBot = async (botId, currentStatus, botName) => {
try {
const action = currentStatus === 'running' ? 'stop' : 'start';
if (action === 'start') {
await botsApi.startBot(botId);
} else {
await botsApi.stopBot(botId);
}
// 로컬 상태만 업데이트 (전체 목록 새로고침 대신)
setBots(prev => prev.map(bot =>
bot.id === botId
? { ...bot, status: action === 'start' ? 'running' : 'stopped' }
: bot
));
setToast({
type: 'success',
message: action === 'start' ? `${botName} 봇이 시작되었습니다.` : `${botName} 봇이 정지되었습니다.`
});
} catch (error) {
console.error('봇 토글 오류:', error);
setToast({ type: 'error', message: error.message || '작업 중 오류가 발생했습니다.' });
}
};
// 전체 동기화
const handleSyncAllVideos = async (botId) => {
setSyncing(botId);
try {
const data = await botsApi.syncAllVideos(botId);
setToast({
type: 'success',
message: `${data.addedCount}개 일정이 추가되었습니다. (전체 ${data.total}개)`
});
fetchBots();
} catch (error) {
console.error('전체 동기화 오류:', error);
setToast({ type: 'error', message: error.message || '동기화 중 오류가 발생했습니다.' });
fetchBots();
} finally {
setSyncing(null);
}
};
// 상태 아이콘 및 색상
const getStatusInfo = (status) => {
switch (status) {
case 'running':
return {
icon:
일정 자동화 봇을 관리합니다
{quotaWarning.message}
등록된 봇이 없습니다
위의 버튼을 클릭하여 봇을 추가하세요
{bot.last_check_at ? `${formatTime(bot.last_check_at)}에 업데이트됨` : '아직 업데이트 없음'}