From 9b2e4e190d34b60b81efc2152d76c524479efcf2 Mon Sep 17 00:00:00 2001 From: caadiq Date: Mon, 1 Jun 2026 14:10:16 +0900 Subject: [PATCH] =?UTF-8?q?feat(mobile-schedule):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?+=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=ED=94=8C=EB=9E=AB=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 일정 리스트 위에 카테고리 필터 칩 추가 (해당 달 전체 카테고리) - 스크롤 방향 감지 자동 숨김 (내리면 숨고 올리면 보임), 상단 여백 일관 - 카테고리 선택 시 일정 목록 + 날짜 점 필터링 (공개 PC와 store 공유) - 일정 리스트 카드: 그림자 제거, 1.5px 테두리로 플랫하게 Co-Authored-By: Claude Opus 4.7 --- .../mobile/schedule/ScheduleListCard.jsx | 4 +- .../src/pages/mobile/schedule/Schedule.jsx | 99 ++++++++++++++++++- 2 files changed, 96 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/mobile/schedule/ScheduleListCard.jsx b/frontend/src/components/mobile/schedule/ScheduleListCard.jsx index 9f21e37..681e28f 100644 --- a/frontend/src/components/mobile/schedule/ScheduleListCard.jsx +++ b/frontend/src/components/mobile/schedule/ScheduleListCard.jsx @@ -27,8 +27,8 @@ const ScheduleListCard = memo(function ScheduleListCard({ onClick={onClick} className={`cursor-pointer ${className}`} > - {/* 카드 본체 */} -
+ {/* 카드 본체 (플랫 테두리) */} +
{/* 시간 및 카테고리 뱃지 */}
diff --git a/frontend/src/pages/mobile/schedule/Schedule.jsx b/frontend/src/pages/mobile/schedule/Schedule.jsx index 00c7a14..5615c63 100644 --- a/frontend/src/pages/mobile/schedule/Schedule.jsx +++ b/frontend/src/pages/mobile/schedule/Schedule.jsx @@ -30,6 +30,9 @@ function MobileSchedule() { const { selectedDate: storedSelectedDate, setSelectedDate: setStoredSelectedDate, + selectedCategories, + setSelectedCategories, + toggleCategory, } = useScheduleStore(); // 선택된 날짜 (store에 없으면 오늘 날짜) @@ -343,15 +346,58 @@ function MobileSchedule() { return days; }, [selectedDate]); - // 선택된 날짜의 일정 (생일 우선) + // 선택된 날짜의 일정 (생일 우선) — 카테고리 필터 반영 const selectedDateSchedules = useMemo(() => { const year = selectedDate.getFullYear(); const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); const day = String(selectedDate.getDate()).padStart(2, '0'); const dateStr = `${year}-${month}-${day}`; // 백엔드에서 이미 정렬된 상태로 전달됨 (특수 일정 우선) - return schedules.filter((s) => s.date.split('T')[0] === dateStr); - }, [schedules, selectedDate]); + return schedules.filter((s) => { + if (s.date.split('T')[0] !== dateStr) return false; + return selectedCategories.length === 0 || selectedCategories.includes(s.category_id); + }); + }, [schedules, selectedDate, selectedCategories]); + + // 해당 달 카테고리 목록 (카운트 포함, 날짜 점 필터용 calendarSchedules도 생성) + const monthCategories = useMemo(() => { + const map = new Map(); + schedules.forEach((s) => { + if (!s.category_id) return; + const existing = map.get(s.category_id); + if (existing) existing.count += 1; + else map.set(s.category_id, { id: s.category_id, name: s.category_name, color: s.category_color, count: 1 }); + }); + return Array.from(map.values()).sort((a, b) => { + if (a.name === '기타') return 1; + if (b.name === '기타') return -1; + return b.count - a.count; + }); + }, [schedules]); + + // 날짜 점 표시용 (카테고리 필터 반영, 날짜는 전체 달 유지) + const dotSchedules = useMemo(() => { + if (selectedCategories.length === 0) return schedules; + return schedules.filter((s) => selectedCategories.includes(s.category_id)); + }, [schedules, selectedCategories]); + + // 카테고리 칩 자동 숨김 (스크롤 방향 감지: 내리면 숨김, 올리면 보임) + const [chipsVisible, setChipsVisible] = useState(true); + const lastScrollYRef = useRef(0); + useEffect(() => { + const container = document.querySelector('.mobile-content'); + if (!container) return; + const onScroll = () => { + const y = container.scrollTop; + const last = lastScrollYRef.current; + if (y < 16) setChipsVisible(true); + else if (y > last + 6) setChipsVisible(false); + else if (y < last - 6) setChipsVisible(true); + lastScrollYRef.current = y; + }; + container.addEventListener('scroll', onScroll, { passive: true }); + return () => container.removeEventListener('scroll', onScroll); + }, []); // 요일 이름 const getDayName = (date) => ['일', '월', '화', '수', '목', '금', '토'][date.getDay()]; @@ -588,7 +634,7 @@ function MobileSchedule() { const day = String(date.getDate()).padStart(2, '0'); const dateStr = `${year}-${month}-${day}`; - const daySchedules = schedules + const daySchedules = dotSchedules .filter((s) => s.date?.split('T')[0] === dateStr) .slice(0, 3); @@ -645,6 +691,7 @@ function MobileSchedule() { })}
)} +
{/* 달력 팝업 */} @@ -778,7 +825,47 @@ function MobileSchedule() {
) - ) : loading ? ( + ) : ( + <> + {/* 카테고리 필터 칩 (스크롤 방향 감지 자동 숨김) */} + {monthCategories.length > 0 && ( +
+
+ + {monthCategories.map((cat) => { + const active = selectedCategories.includes(cat.id); + return ( + + ); + })} +
+
+ )} + + {loading ? (
@@ -830,6 +917,8 @@ function MobileSchedule() { ); })}
+ )} + )}