diff --git a/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx b/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx
new file mode 100644
index 0000000..0e155ee
--- /dev/null
+++ b/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx
@@ -0,0 +1,86 @@
+import { memo } from 'react';
+import { motion } from 'framer-motion';
+import { Link2 } from 'lucide-react';
+import { decodeHtmlEntities, getDisplayMembers, getCategoryInfo } from '@/utils';
+
+/**
+ * Mobile용 날짜 미정(월만 확정) 일정 카드
+ * date_precision === 'month' 인 일정에 사용. 시간 대신 "N월 중"을 표시하고
+ * 점선 테두리로 확정 일정과 구분한다.
+ */
+const UndatedScheduleListCard = memo(function UndatedScheduleListCard({
+ schedule,
+ onClick,
+ delay = 0,
+ className = '',
+}) {
+ const categoryInfo = getCategoryInfo(schedule);
+ const displayMembers = getDisplayMembers(schedule);
+ const sourceName = schedule.source?.name;
+
+ // date는 해당 월 1일(YYYY-MM-01)로 저장됨 → 월만 추출
+ const month = new Date(schedule.date).getMonth() + 1;
+
+ return (
+
+ {/* 카드 본체 (점선 테두리) */}
+
+
+ {/* "N월 중" 및 카테고리 뱃지 */}
+
+
+ {month}월 중
+
+
+ {categoryInfo.name}
+
+
+
+ {/* 제목 */}
+
+ {decodeHtmlEntities(schedule.title)}
+
+
+ {/* 출처 */}
+ {sourceName && (
+
+
+ {sourceName}
+
+ )}
+
+ {/* 멤버 */}
+ {displayMembers.length > 0 && (
+
+ {displayMembers.map((name, i) => (
+
+ {name}
+
+ ))}
+
+ )}
+
+
+
+ );
+});
+
+export default UndatedScheduleListCard;
diff --git a/frontend/src/components/mobile/schedule/index.js b/frontend/src/components/mobile/schedule/index.js
index 6cff5ca..ed5d484 100644
--- a/frontend/src/components/mobile/schedule/index.js
+++ b/frontend/src/components/mobile/schedule/index.js
@@ -1,6 +1,7 @@
export { default as Calendar } from './Calendar';
export { default as ScheduleCard } from './ScheduleCard';
export { default as ScheduleListCard } from './ScheduleListCard';
+export { default as UndatedScheduleListCard } from './UndatedScheduleListCard';
export { default as ScheduleSearchCard } from './ScheduleSearchCard';
export { default as BirthdayCard } from './BirthdayCard';
export { default as DebutCard } from './DebutCard';
diff --git a/frontend/src/pages/mobile/schedule/Schedule.jsx b/frontend/src/pages/mobile/schedule/Schedule.jsx
index 177b287..2ce141d 100644
--- a/frontend/src/pages/mobile/schedule/Schedule.jsx
+++ b/frontend/src/pages/mobile/schedule/Schedule.jsx
@@ -13,6 +13,7 @@ import { MIN_YEAR, SEARCH_LIMIT } from '@/constants';
import {
Calendar as MobileCalendar,
ScheduleListCard as MobileScheduleListCard,
+ UndatedScheduleListCard as MobileUndatedScheduleListCard,
ScheduleSearchCard as MobileScheduleSearchCard,
BirthdayCard as MobileBirthdayCard,
DebutCard as MobileDebutCard,
@@ -354,11 +355,20 @@ function MobileSchedule() {
const dateStr = `${year}-${month}-${day}`;
// 백엔드에서 이미 정렬된 상태로 전달됨 (특수 일정 우선)
return schedules.filter((s) => {
+ if (s.datePrecision === 'month') return false; // 날짜 미정은 별도 처리
if (s.date.split('T')[0] !== dateStr) return false;
return selectedCategories.length === 0 || selectedCategories.includes(s.category_id);
});
}, [schedules, selectedDate, selectedCategories]);
+ // 날짜 미정(월만 확정) 일정 — 선택 날짜와 무관하게 해당 달이면 항상 하단에 표시
+ const undatedSchedules = useMemo(() => {
+ return schedules.filter((s) => {
+ if (s.datePrecision !== 'month') return false;
+ return selectedCategories.length === 0 || selectedCategories.includes(s.category_id);
+ });
+ }, [schedules, selectedCategories]);
+
// 해당 달 카테고리 목록 (카운트 포함, 날짜 점 필터용 calendarSchedules도 생성)
const monthCategories = useMemo(() => {
const map = new Map();
@@ -376,9 +386,12 @@ function MobileSchedule() {
}, [schedules]);
// 날짜 점 표시용 (카테고리 필터 반영, 날짜는 전체 달 유지)
+ // 날짜 미정 일정은 점을 찍지 않음 (특정 날짜에 속하지 않으므로)
const dotSchedules = useMemo(() => {
- if (selectedCategories.length === 0) return schedules;
- return schedules.filter((s) => selectedCategories.includes(s.category_id));
+ return schedules.filter((s) => {
+ if (s.datePrecision === 'month') return false;
+ return selectedCategories.length === 0 || selectedCategories.includes(s.category_id);
+ });
}, [schedules, selectedCategories]);
// 요일 이름
@@ -848,7 +861,7 @@ function MobileSchedule() {
- ) : selectedDateSchedules.length === 0 ? (
+ ) : selectedDateSchedules.length === 0 && undatedSchedules.length === 0 ? (
)}
>
diff --git a/frontend/src/pages/pc/public/schedule/Schedule.jsx b/frontend/src/pages/pc/public/schedule/Schedule.jsx
index 79aa224..ae96c69 100644
--- a/frontend/src/pages/pc/public/schedule/Schedule.jsx
+++ b/frontend/src/pages/pc/public/schedule/Schedule.jsx
@@ -275,9 +275,12 @@ function PCSchedule() {
}, [schedules, currentYearMonth, selectedCategories, isSearchMode]);
// 달력 점 표시용 (카테고리만 필터링, 날짜는 한 달 전체 유지)
+ // 날짜 미정 일정은 점을 찍지 않음 (특정 날짜에 속하지 않으므로)
const calendarSchedules = useMemo(() => {
- if (selectedCategories.length === 0) return schedules;
- return schedules.filter((s) => selectedCategories.includes(s.category_id));
+ return schedules.filter((s) => {
+ if (s.datePrecision === 'month') return false;
+ return selectedCategories.length === 0 || selectedCategories.includes(s.category_id);
+ });
}, [schedules, selectedCategories]);
// 가상 스크롤