일정 관리 상태 관리를 Zustand로 마이그레이션
- zustand 패키지 설치 - useScheduleStore 스토어 생성 - sessionStorage 관련 모든 복잡한 로직 제거 - 메모리 기반이라 SPA 내 이동 시 유지, 새로고침 시 자동 초기화
This commit is contained in:
parent
7df7469b78
commit
dac2234a0b
4 changed files with 92 additions and 61 deletions
32
frontend/package-lock.json
generated
32
frontend/package-lock.json
generated
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
36
frontend/src/stores/useScheduleStore.js
Normal file
36
frontend/src/stores/useScheduleStore.js
Normal 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;
|
||||
Loading…
Add table
Reference in a new issue