일정 관리 상태 관리를 Zustand로 마이그레이션

- zustand 패키지 설치
- useScheduleStore 스토어 생성
- sessionStorage 관련 모든 복잡한 로직 제거
- 메모리 기반이라 SPA 내 이동 시 유지, 새로고침 시 자동 초기화
This commit is contained in:
caadiq 2026-01-06 12:26:40 +09:00
parent 7df7469b78
commit dac2234a0b
4 changed files with 92 additions and 61 deletions

View file

@ -17,7 +17,8 @@
"react-ios-time-picker": "^0.2.2", "react-ios-time-picker": "^0.2.2",
"react-photo-album": "^3.4.0", "react-photo-album": "^3.4.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-window": "^2.2.3" "react-window": "^2.2.3",
"zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
@ -2826,6 +2827,35 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
},
"node_modules/zustand": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
"integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View file

@ -18,7 +18,8 @@
"react-ios-time-picker": "^0.2.2", "react-ios-time-picker": "^0.2.2",
"react-photo-album": "^3.4.0", "react-photo-album": "^3.4.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-window": "^2.2.3" "react-window": "^2.2.3",
"zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.3", "@types/react": "^18.3.3",

View file

@ -8,6 +8,7 @@ import {
import Toast from '../../../components/Toast'; import Toast from '../../../components/Toast';
import Tooltip from '../../../components/Tooltip'; import Tooltip from '../../../components/Tooltip';
import useScheduleStore from '../../../stores/useScheduleStore';
function AdminSchedule() { function AdminSchedule() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -20,47 +21,30 @@ function AdminSchedule() {
return kstDate.toISOString().split('T')[0]; return kstDate.toISOString().split('T')[0];
}; };
// sessionStorage ( ) // Zustand
// (fromScheduleForm ) const {
const stateRestoredRef = useRef(false); searchInput, setSearchInput,
const getStoredState = () => { searchTerm, setSearchTerm,
// (StrictMode ) isSearchMode, setIsSearchMode,
if (stateRestoredRef.current) { selectedCategories, setSelectedCategories,
const stored = sessionStorage.getItem('adminScheduleState'); selectedDate, setSelectedDate,
return stored ? JSON.parse(stored) : null; currentDate, setCurrentDate,
} } = useScheduleStore();
stateRestoredRef.current = true;
try {
const fromForm = sessionStorage.getItem('fromScheduleForm');
if (fromForm) {
//
sessionStorage.removeItem('fromScheduleForm');
const stored = sessionStorage.getItem('adminScheduleState');
return stored ? JSON.parse(stored) : null;
}
//
sessionStorage.removeItem('adminScheduleState');
return null;
} catch { return null; }
};
const storedState = getStoredState();
// ( )
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [toast, setToast] = useState(null); const [toast, setToast] = useState(null);
const [searchInput, setSearchInput] = useState(storedState?.searchInput || '');
const [searchTerm, setSearchTerm] = useState(storedState?.searchTerm || '');
const [isSearchMode, setIsSearchMode] = useState(storedState?.isSearchMode || false);
const [searchResults, setSearchResults] = useState([]); const [searchResults, setSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false); const [searchLoading, setSearchLoading] = useState(false);
const [selectedCategories, setSelectedCategories] = useState(storedState?.selectedCategories || []);
const [selectedDate, setSelectedDate] = useState(storedState?.selectedDate || getTodayKST()); // selectedDate
const [currentDate, setCurrentDate] = useState( useEffect(() => {
storedState?.currentDate ? new Date(storedState.currentDate) : new Date() if (!selectedDate) {
); setSelectedDate(getTodayKST());
}
}, []);
const [slideDirection, setSlideDirection] = useState(0); const [slideDirection, setSlideDirection] = useState(0);
@ -197,18 +181,7 @@ function AdminSchedule() {
fetchSchedules(); fetchSchedules();
}, [year, month]); }, [year, month]);
// sessionStorage ( )
useEffect(() => {
const stateToSave = {
searchInput,
searchTerm,
isSearchMode,
selectedCategories,
selectedDate,
currentDate: currentDate.toISOString(),
};
sessionStorage.setItem('adminScheduleState', JSON.stringify(stateToSave));
}, [searchInput, searchTerm, isSearchMode, selectedCategories, selectedDate, currentDate]);
// //
useEffect(() => { useEffect(() => {
@ -526,20 +499,14 @@ function AdminSchedule() {
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={() => { onClick={() => navigate('/admin/schedule/bots')}
sessionStorage.setItem('fromScheduleForm', 'true');
navigate('/admin/schedule/bots');
}}
className="flex items-center gap-2 px-5 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors font-medium" className="flex items-center gap-2 px-5 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors font-medium"
> >
<Bot size={20} /> <Bot size={20} />
관리 관리
</button> </button>
<button <button
onClick={() => { onClick={() => navigate('/admin/schedule/new')}
sessionStorage.setItem('fromScheduleForm', 'true');
navigate('/admin/schedule/new');
}}
className="flex items-center gap-2 px-5 py-3 bg-primary text-white rounded-xl hover:bg-primary-dark transition-colors font-medium shadow-sm" className="flex items-center gap-2 px-5 py-3 bg-primary text-white rounded-xl hover:bg-primary-dark transition-colors font-medium shadow-sm"
> >
<Plus size={20} /> <Plus size={20} />
@ -1086,10 +1053,7 @@ function AdminSchedule() {
</a> </a>
)} )}
<button <button
onClick={() => { onClick={() => navigate(`/admin/schedule/${schedule.id}/edit`)}
sessionStorage.setItem('fromScheduleForm', 'true');
navigate(`/admin/schedule/${schedule.id}/edit`);
}}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors text-gray-500" className="p-2 hover:bg-gray-200 rounded-lg transition-colors text-gray-500"
> >
<Edit2 size={18} /> <Edit2 size={18} />

View file

@ -0,0 +1,36 @@
import { create } from "zustand";
// 일정 관리 페이지 상태 스토어
// 메모리 기반이므로 SPA 내 페이지 이동 시 유지, 새로고침 시 초기화
const useScheduleStore = create((set) => ({
// 검색 관련
searchInput: "",
searchTerm: "",
isSearchMode: false,
// 필터 및 선택
selectedCategories: [],
selectedDate: null, // null이면 getTodayKST() 사용
currentDate: new Date(),
// 상태 업데이트 함수
setSearchInput: (value) => set({ searchInput: value }),
setSearchTerm: (value) => set({ searchTerm: value }),
setIsSearchMode: (value) => set({ isSearchMode: value }),
setSelectedCategories: (value) => set({ selectedCategories: value }),
setSelectedDate: (value) => set({ selectedDate: value }),
setCurrentDate: (value) => set({ currentDate: value }),
// 상태 초기화
reset: () =>
set({
searchInput: "",
searchTerm: "",
isSearchMode: false,
selectedCategories: [],
selectedDate: null,
currentDate: new Date(),
}),
}));
export default useScheduleStore;