일정 관리 상태 관리를 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-photo-album": "^3.4.0",
"react-router-dom": "^6.22.3",
"react-window": "^2.2.3"
"react-window": "^2.2.3",
"zustand": "^5.0.9"
},
"devDependencies": {
"@types/react": "^18.3.3",
@ -2826,6 +2827,35 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"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-photo-album": "^3.4.0",
"react-router-dom": "^6.22.3",
"react-window": "^2.2.3"
"react-window": "^2.2.3",
"zustand": "^5.0.9"
},
"devDependencies": {
"@types/react": "^18.3.3",

View file

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