일정 페이지 UI/UX 개선
- 검색 모드 전환 시 일정 목록 fade 애니메이션 통일 - 일정 개수 텍스트 애니메이션 추가 - 관리자 일정 개수 표시 'N개 일정'으로 변경 - 일정 항목 애니메이션 y 이동 제거 (스크롤바 깜빡임 방지) - 관리자 일정 페이지 상태 유지 (sessionStorage)
This commit is contained in:
parent
2572ce2195
commit
b6b212821e
2 changed files with 76 additions and 21 deletions
|
|
@ -687,16 +687,34 @@ function Schedule() {
|
|||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{/* 검색 모드가 아닐 때만 개수 표시 */}
|
||||
<AnimatePresence>
|
||||
{!isSearchMode && (
|
||||
<span className="text-sm text-gray-500">{filteredSchedules.length}개 일정</span>
|
||||
<motion.span
|
||||
key="count"
|
||||
initial={{ opacity: 0, x: 10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 10 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="text-sm text-gray-500"
|
||||
>
|
||||
{filteredSchedules.length}개 일정
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="max-h-[calc(100vh-200px)] overflow-y-auto space-y-4 pr-2 py-2">
|
||||
<motion.div
|
||||
key={isSearchMode ? 'search' : 'normal'}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="max-h-[calc(100vh-200px)] overflow-y-auto space-y-4 py-2 pr-2"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="text-center py-20 text-gray-500">로딩 중...</div>
|
||||
) : filteredSchedules.length > 0 ? (
|
||||
|
||||
filteredSchedules.map((schedule, index) => {
|
||||
const formatted = formatDate(schedule.date);
|
||||
const categoryColor = getCategoryColor(schedule.category_id);
|
||||
|
|
@ -704,10 +722,11 @@ function Schedule() {
|
|||
|
||||
return (
|
||||
<motion.div
|
||||
key={schedule.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
key={`${schedule.id}-${selectedDate || 'all'}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: Math.min(index, 10) * 0.03 }}
|
||||
|
||||
onClick={() => handleScheduleClick(schedule)}
|
||||
className="flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden cursor-pointer"
|
||||
>
|
||||
|
|
@ -788,8 +807,9 @@ function Schedule() {
|
|||
{selectedDate ? '선택한 날짜에 일정이 없습니다.' : '예정된 일정이 없습니다.'}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,17 +19,29 @@ function AdminSchedule() {
|
|||
return kstDate.toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
// sessionStorage에서 저장된 상태 복원
|
||||
const getStoredState = () => {
|
||||
try {
|
||||
const stored = sessionStorage.getItem('adminScheduleState');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
} catch { return null; }
|
||||
};
|
||||
const storedState = getStoredState();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [user, setUser] = useState(null);
|
||||
const [toast, setToast] = useState(null);
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [isSearchMode, setIsSearchMode] = useState(false);
|
||||
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([]);
|
||||
const [selectedDate, setSelectedDate] = useState(getTodayKST()); // KST 기준 오늘
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
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()
|
||||
);
|
||||
|
||||
|
||||
const [slideDirection, setSlideDirection] = useState(0);
|
||||
|
||||
|
|
@ -157,6 +169,27 @@ 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(() => {
|
||||
if (isSearchMode && searchTerm) {
|
||||
searchSchedules(searchTerm);
|
||||
}
|
||||
}, []); // 컴포넌트 마운트 시에만
|
||||
|
||||
|
||||
// 카테고리 로드 함수
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
|
|
@ -882,7 +915,7 @@ function AdminSchedule() {
|
|||
className="flex items-center gap-1 px-2 py-1 bg-gray-100 rounded-md text-sm text-gray-600 hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
<Tag size={14} />
|
||||
<span>{selectedCategories.length}개</span>
|
||||
<span>{selectedCategories.length}개 일정</span>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{showCategoryTooltip && (
|
||||
|
|
@ -911,7 +944,7 @@ function AdminSchedule() {
|
|||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm text-gray-400">{filteredSchedules.length}개</span>
|
||||
<span className="text-sm text-gray-400">{filteredSchedules.length}개 일정</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
|
@ -928,14 +961,16 @@ function AdminSchedule() {
|
|||
<p>등록된 일정이 없습니다</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[calc(100vh-280px)] overflow-y-auto divide-y divide-gray-100 pr-2 py-2">
|
||||
<div className="max-h-[calc(100vh-280px)] overflow-y-auto divide-y divide-gray-100 py-2">
|
||||
|
||||
{filteredSchedules.map((schedule, index) => (
|
||||
<motion.div
|
||||
key={schedule.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
key={`${schedule.id}-${selectedDate || 'all'}`}
|
||||
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: Math.min(index, 10) * 0.03 }}
|
||||
|
||||
className="p-6 hover:bg-gray-50 transition-colors group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue