diff --git a/frontend/src/pages/pc/admin/AdminScheduleDict.jsx b/frontend/src/pages/pc/admin/AdminScheduleDict.jsx index 19e9242..082b0f8 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleDict.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleDict.jsx @@ -1,6 +1,7 @@ -import { useState, useEffect, useMemo, useRef } from 'react'; +import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Home, ChevronRight, Book, Plus, Trash2, Search, ChevronDown } from 'lucide-react'; import Toast from '../../../components/Toast'; import AdminLayout from '../../../components/admin/AdminLayout'; @@ -171,8 +172,8 @@ function WordItem({ id, word, pos, index, onUpdate, onDelete }) { function AdminScheduleDict() { const { user, isAuthenticated } = useAdminAuth(); const { toast, setToast } = useToast(); + const queryClient = useQueryClient(); const [entries, setEntries] = useState([]); // [{word, pos, isComment, id}] - const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [filterPos, setFilterPos] = useState('all'); @@ -239,55 +240,59 @@ function AdminScheduleDict() { return stats; }, [entries]); - useEffect(() => { - if (isAuthenticated) { - fetchDict(); - } - }, [isAuthenticated]); - // 고유 ID 생성 - const generateId = () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const generateId = useCallback(() => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, []); // 사전 파일 파싱 - const parseDict = (content) => { + const parseDict = useCallback((content) => { const lines = content.split('\n'); return lines.map(line => { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) { - return { isComment: true, raw: line, id: generateId() }; + return { isComment: true, raw: line, id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` }; } const parts = trimmed.split('\t'); return { word: parts[0] || '', pos: parts[1] || 'NNP', isComment: false, - id: generateId(), + id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, }; }).filter(e => e.isComment || e.word); // 빈 줄 제거하되 주석은 유지 - }; + }, []); // 사전 파일 생성 - const serializeDict = (entries) => { + const serializeDict = useCallback((entries) => { return entries.map(e => { if (e.isComment) return e.raw; return `${e.word}\t${e.pos}`; }).join('\n'); - }; + }, []); - // 사전 내용 조회 - const fetchDict = async () => { - setLoading(true); - try { + // 사전 내용 조회 (useQuery) + const { data: dictContent, isLoading: loading, isError } = useQuery({ + queryKey: ['admin', 'dict'], + queryFn: async () => { const data = await suggestionsApi.getDict(); - const parsed = parseDict(data.content || ''); + return data.content || ''; + }, + enabled: isAuthenticated, + }); + + // 사전 데이터 로드 후 파싱 + useEffect(() => { + if (dictContent !== undefined) { + const parsed = parseDict(dictContent); setEntries(parsed); - } catch (error) { - console.error('사전 조회 오류:', error); - setToast({ type: 'error', message: '사전을 불러올 수 없습니다.' }); - } finally { - setLoading(false); } - }; + }, [dictContent, parseDict]); + + // 에러 처리 + useEffect(() => { + if (isError) { + setToast({ type: 'error', message: '사전을 불러올 수 없습니다.' }); + } + }, [isError, setToast]); // 사전 저장 (entries 배열을 받아서 저장) const saveDict = async (newEntries) => { diff --git a/frontend/src/pages/pc/admin/schedule/edit/YouTubeEditForm.jsx b/frontend/src/pages/pc/admin/schedule/edit/YouTubeEditForm.jsx index 9047af8..1e10892 100644 --- a/frontend/src/pages/pc/admin/schedule/edit/YouTubeEditForm.jsx +++ b/frontend/src/pages/pc/admin/schedule/edit/YouTubeEditForm.jsx @@ -92,7 +92,7 @@ function YouTubeEditForm() { return; } setSelectedMembers(schedule.members?.map((m) => m.id) || []); - setVideoType(schedule.youtube?.videoType || "video"); + setVideoType(schedule.videoType || "video"); setIsInitialized(true); } }, [schedule, isInitialized, navigate, setToast]); @@ -178,12 +178,14 @@ function YouTubeEditForm() { } const videoUrl = videoType === "shorts" - ? `https://www.youtube.com/shorts/${schedule.youtube?.videoId}` - : `https://www.youtube.com/watch?v=${schedule.youtube?.videoId}`; + ? `https://www.youtube.com/shorts/${schedule.videoId}` + : `https://www.youtube.com/watch?v=${schedule.videoId}`; - // 날짜 포맷팅 함수 - const formatDate = (dateStr, timeStr) => { - if (!dateStr) return ""; + // 날짜 포맷팅 함수 (datetime 문자열 파싱) + const formatDatetime = (datetime) => { + if (!datetime) return ""; + // datetime: "2025-01-20 14:00" 또는 "2025-01-20" + const [dateStr, timeStr] = datetime.split(" "); const date = new Date(dateStr); const year = date.getFullYear(); const month = date.getMonth() + 1; @@ -239,7 +241,7 @@ function YouTubeEditForm() {