diff --git a/frontend/src/pages/mobile/public/Schedule.jsx b/frontend/src/pages/mobile/public/Schedule.jsx index 144a825..d6a52cd 100644 --- a/frontend/src/pages/mobile/public/Schedule.jsx +++ b/frontend/src/pages/mobile/public/Schedule.jsx @@ -3,6 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { Clock, Tag, Link2, ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react'; import { useInfiniteQuery } from '@tanstack/react-query'; import { useInView } from 'react-intersection-observer'; +import { useVirtualizer } from '@tanstack/react-virtual'; import { getSchedules, getCategories, searchSchedules } from '../../../api/public/schedules'; // 모바일 일정 페이지 @@ -26,7 +27,9 @@ function MobileSchedule() { setCalendarViewDate(newDate); }; - const SEARCH_LIMIT = 10; + const SEARCH_LIMIT = 20; // 페이지당 20개 + const ESTIMATED_ITEM_HEIGHT = 100; // 아이템 추정 높이 (동적 측정) + const scrollContainerRef = useRef(null); // 가상 스크롤 컨테이너 const { ref: loadMoreRef, inView } = useInView({ threshold: 0, rootMargin: '100px' }); // 검색 무한 스크롤 @@ -55,6 +58,14 @@ function MobileSchedule() { return searchData.pages.flatMap(page => page.schedules); }, [searchData]); + // 가상 스크롤 설정 (검색 모드에서만 활성화, 동적 높이 지원) + const virtualizer = useVirtualizer({ + count: isSearchMode && searchTerm ? searchResults.length : 0, + getScrollElement: () => scrollContainerRef.current, + estimateSize: () => ESTIMATED_ITEM_HEIGHT, + overscan: 5, // 버퍼 아이템 수 + }); + useEffect(() => { if (inView && hasNextPage && !isFetchingNextPage && isSearchMode && searchTerm) { fetchNextPage(); @@ -436,39 +447,65 @@ function MobileSchedule() { {/* 컨텐츠 영역 */} -