일정 관리 상태 관리를 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-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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
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