perf: Schedule 페이지 성능 최적화

- useDeferredValue로 달력 점 표시 지연 처리
- scheduleDateMap으로 O(1) 조회 최적화
- selectedDate 변경 시 스크롤 맨 위로 초기화
This commit is contained in:
caadiq 2026-01-09 20:34:26 +09:00
parent 8db0a574ab
commit 2b6fa74eb8

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useMemo } from 'react'; import { useState, useEffect, useRef, useMemo, useDeferredValue, memo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { Clock, ChevronLeft, ChevronRight, ChevronDown, Tag, Search, ArrowLeft, Link2 } from 'lucide-react'; import { Clock, ChevronLeft, ChevronRight, ChevronDown, Tag, Search, ArrowLeft, Link2 } from 'lucide-react';
@ -26,6 +26,7 @@ function Schedule() {
// //
const [showCategoryTooltip, setShowCategoryTooltip] = useState(false); const [showCategoryTooltip, setShowCategoryTooltip] = useState(false);
const categoryRef = useRef(null); const categoryRef = useRef(null);
const scrollContainerRef = useRef(null); //
// //
const [isSearchMode, setIsSearchMode] = useState(false); const [isSearchMode, setIsSearchMode] = useState(false);
@ -128,6 +129,13 @@ function Schedule() {
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, []);
//
useEffect(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = 0;
}
}, [selectedDate]);
// //
const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate(); const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay(); const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay();
@ -139,21 +147,34 @@ function Schedule() {
const days = ['일', '월', '화', '수', '목', '금', '토']; const days = ['일', '월', '화', '수', '목', '금', '토'];
// (ISO YYYY-MM-DD ) // UI
const scheduleDates = schedules.map(s => s.date ? s.date.split('T')[0] : ''); const deferredSchedules = useDeferredValue(schedules);
// // (O(1) ) -
const scheduleDateMap = useMemo(() => {
const map = new Map();
deferredSchedules.forEach(s => {
const dateStr = s.date ? s.date.split('T')[0] : '';
if (!map.has(dateStr)) {
map.set(dateStr, s);
}
});
return map;
}, [deferredSchedules]);
// (O(1))
const getScheduleColor = (day) => { const getScheduleColor = (day) => {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const schedule = schedules.find(s => (s.date ? s.date.split('T')[0] : '') === dateStr); const schedule = scheduleDateMap.get(dateStr);
if (!schedule) return null; if (!schedule) return null;
const cat = categories.find(c => c.id === schedule.category_id); const cat = categories.find(c => c.id === schedule.category_id);
return cat?.color || '#4A7C59'; return cat?.color || '#4A7C59';
}; };
// (O(1))
const hasSchedule = (day) => { const hasSchedule = (day) => {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
return scheduleDates.includes(dateStr); return scheduleDateMap.has(dateStr);
}; };
const prevMonth = () => { const prevMonth = () => {
@ -768,6 +789,7 @@ function Schedule() {
</div> </div>
<div <div
ref={scrollContainerRef}
id="scheduleScrollContainer" id="scheduleScrollContainer"
className="max-h-[calc(100vh-200px)] overflow-y-auto space-y-4 py-2 pr-2" className="max-h-[calc(100vh-200px)] overflow-y-auto space-y-4 py-2 pr-2"
> >