From e3278c81dea27b8b32c5dca8d04c545f621af5d5 Mon Sep 17 00:00:00 2001 From: caadiq Date: Wed, 21 Jan 2026 14:25:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=EB=B4=87=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20useEffect=20=E2=86=92=20useQue?= =?UTF-8?q?ry=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - React 18 Strict Mode 중복 요청 방지 - getBots, getQuotaWarning을 useQuery로 전환 - toggleBot에서 queryClient.setQueryData 사용 Co-Authored-By: Claude Opus 4.5 --- .../src/pages/pc/admin/AdminScheduleBots.jsx | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/frontend/src/pages/pc/admin/AdminScheduleBots.jsx b/frontend/src/pages/pc/admin/AdminScheduleBots.jsx index 7b59e56..813a093 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleBots.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleBots.jsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; import { useNavigate, Link } from 'react-router-dom'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { motion, AnimatePresence } from 'framer-motion'; -import { +import { Home, ChevronRight, Bot, Play, Square, Youtube, Calendar, Clock, CheckCircle, XCircle, RefreshCw, Download } from 'lucide-react'; @@ -44,46 +45,42 @@ const MeilisearchIcon = ({ size = 20 }) => ( function AdminScheduleBots() { const navigate = useNavigate(); + const queryClient = useQueryClient(); const { user, isAuthenticated } = useAdminAuth(); 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(() => { - if (isAuthenticated) { - fetchBots(); - fetchQuotaWarning(); - } - }, [isAuthenticated]); - // 봇 목록 조회 - 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 { data: bots = [], isLoading: loading, isError, refetch: fetchBots } = useQuery({ + queryKey: ['admin', 'bots'], + queryFn: botsApi.getBots, + enabled: isAuthenticated, + staleTime: 30000, + }); // 할당량 경고 상태 조회 - const fetchQuotaWarning = async () => { - try { - const data = await botsApi.getQuotaWarning(); - if (data.active) { - setQuotaWarning(data); - } - } catch (error) { - console.error('할당량 경고 조회 오류:', error); + const { data: quotaData } = useQuery({ + queryKey: ['admin', 'bots', 'quota'], + queryFn: botsApi.getQuotaWarning, + enabled: isAuthenticated, + staleTime: 60000, + }); + + // 에러 처리 + useEffect(() => { + if (isError) { + setToast({ type: 'error', message: '봇 목록을 불러올 수 없습니다.' }); } - }; + }, [isError, setToast]); + + // 할당량 경고 상태 업데이트 + useEffect(() => { + if (quotaData?.active) { + setQuotaWarning(quotaData); + } + }, [quotaData]); // 할당량 경고 해제 const handleDismissQuotaWarning = async () => { @@ -99,21 +96,23 @@ function AdminScheduleBots() { 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', + + // 캐시 업데이트 (전체 목록 새로고침 대신) + queryClient.setQueryData(['admin', 'bots'], (prev) => + prev?.map(bot => + bot.id === botId + ? { ...bot, status: action === 'start' ? 'running' : 'stopped' } + : bot + ) + ); + setToast({ + type: 'success', message: action === 'start' ? `${botName} 봇이 시작되었습니다.` : `${botName} 봇이 정지되었습니다.` }); } catch (error) {